TechDraw: SVG export refactoring - All XML modifications via DOM
This commit is contained in:
committed by
WandererFan
parent
b28b22f08a
commit
a562f6a129
@@ -17,6 +17,7 @@ include_directories(
|
||||
${PYTHON_INCLUDE_DIRS}
|
||||
${XercesC_INCLUDE_DIRS}
|
||||
${QT_QTCORE_INCLUDE_DIR}
|
||||
${QT_QTXMLPATTERNS_INCLUDE_DIR}
|
||||
)
|
||||
link_directories(${OCC_LIBRARY_DIR})
|
||||
|
||||
@@ -99,6 +100,8 @@ SET(Draw_SRCS
|
||||
DrawLeaderLine.h
|
||||
DrawRichAnno.cpp
|
||||
DrawRichAnno.h
|
||||
QDomNodeModel.cpp
|
||||
QDomNodeModel.h
|
||||
)
|
||||
|
||||
SET(TechDraw_SRCS
|
||||
@@ -177,7 +180,7 @@ endif(FREECAD_USE_PCH)
|
||||
|
||||
add_library(TechDraw SHARED ${TechDraw_SRCS} ${Draw_SRCS} ${TechDrawAlgos_SRCS}
|
||||
${Geometry_SRCS} ${Python_SRCS})
|
||||
target_link_libraries(TechDraw ${TechDrawLIBS};${QT_QTXML_LIBRARY};${TechDraw})
|
||||
target_link_libraries(TechDraw ${TechDrawLIBS};${QT_QTXML_LIBRARY};${QT_QTXMLPATTERNS_LIBRARY};${TechDraw})
|
||||
|
||||
ADD_CUSTOM_COMMAND(TARGET TechDraw
|
||||
POST_BUILD
|
||||
|
||||
@@ -29,6 +29,10 @@
|
||||
#include <QFile>
|
||||
#endif
|
||||
|
||||
#include <QXmlQuery>
|
||||
#include <QXmlResultItems>
|
||||
#include "QDomNodeModel.h"
|
||||
|
||||
#include <Base/Exception.h>
|
||||
#include <Base/Console.h>
|
||||
#include <Base/Interpreter.h>
|
||||
@@ -125,18 +129,18 @@ void DrawSVGTemplate::onChanged(const App::Property* prop)
|
||||
|
||||
App::DocumentObjectExecReturn * DrawSVGTemplate::execute(void)
|
||||
{
|
||||
std::string templValue = Template.getValue();
|
||||
if (templValue.empty())
|
||||
std::string templateFilename = Template.getValue();
|
||||
if (templateFilename.empty())
|
||||
return App::DocumentObject::StdReturn;
|
||||
|
||||
Base::FileInfo fi(templValue);
|
||||
Base::FileInfo fi(templateFilename);
|
||||
if (!fi.isReadable()) {
|
||||
// non-empty template value, but can't read file
|
||||
// if there is a old absolute template file set use a redirect
|
||||
fi.setFile(App::Application::getResourceDir() + "Mod/Drawing/Templates/" + fi.fileName());
|
||||
// try the redirect
|
||||
if (!fi.isReadable()) {
|
||||
Base::Console().Log("DrawPage::execute() not able to open %s!\n",Template.getValue());
|
||||
Base::Console().Log("DrawPage::execute() not able to open %s!\n", Template.getValue());
|
||||
std::string error = std::string("Cannot open file ") + Template.getValue();
|
||||
return new App::DocumentObjectExecReturn(error);
|
||||
}
|
||||
@@ -145,99 +149,81 @@ App::DocumentObjectExecReturn * DrawSVGTemplate::execute(void)
|
||||
if (std::string(PageResult.getValue()).empty()) //first time through?
|
||||
PageResult.setValue(fi.filePath().c_str());
|
||||
|
||||
// open Template file
|
||||
string line;
|
||||
ifstream inTemplate (fi.filePath().c_str());
|
||||
QFile templateFile(QString::fromUtf8(fi.filePath().c_str()));
|
||||
if (!templateFile.open(QIODevice::ReadOnly)) {
|
||||
Base::Console().Log("DrawPage::execute() can't read template %s!\n", Template.getValue());
|
||||
std::string error = std::string("Cannot read file ") + Template.getValue();
|
||||
return new App::DocumentObjectExecReturn(error);
|
||||
}
|
||||
|
||||
ostringstream copyTemplate;
|
||||
string tempendl = "--endOfLine--";
|
||||
QDomDocument templateDocument;
|
||||
if (!templateDocument.setContent(&templateFile)) {
|
||||
Base::Console().Message("DrawPage::execute() - failed to parse file: %s\n",
|
||||
Template.getValue());
|
||||
std::string error = std::string("Cannot parse file ") + Template.getValue();
|
||||
return new App::DocumentObjectExecReturn(error);
|
||||
}
|
||||
|
||||
//inTemplate to copyTemplate
|
||||
//remove DrawingContent comment line
|
||||
//change line endings
|
||||
//capture TitleBlock dimensions
|
||||
while (getline(inTemplate,line))
|
||||
QXmlQuery query(QXmlQuery::XQuery10);
|
||||
QDomNodeModel model(query.namePool(), templateDocument);
|
||||
query.setFocus(QXmlItem(model.fromDomNode(templateDocument.documentElement())));
|
||||
|
||||
// XPath query to select all <tspan> nodes whose <text> parent
|
||||
// has "freecad:editable" attribute
|
||||
query.setQuery(QString::fromUtf8(
|
||||
"declare default element namespace \"" SVG_NS_URI "\"; "
|
||||
"declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; "
|
||||
"//text[@freecad:editable]/tspan"));
|
||||
|
||||
QXmlResultItems queryResult;
|
||||
query.evaluateTo(&queryResult);
|
||||
|
||||
std::map<std::string, std::string> substitutions = EditableTexts.getValues();
|
||||
while (!queryResult.next().isNull())
|
||||
{
|
||||
// copy every line except the DrawingContent comment?
|
||||
if(line.find("<!-- DrawingContent -->") == string::npos) {
|
||||
// if not - write through
|
||||
copyTemplate << line << tempendl;
|
||||
}
|
||||
QDomElement tspanTempl = model.toDomNode(queryResult.current().toNodeModelIndex()).toElement();
|
||||
|
||||
//double t0, t1,t2,t3;
|
||||
float t0, t1,t2,t3;
|
||||
if(line.find("<!-- Title block") != std::string::npos) {
|
||||
(void) sscanf(line.c_str(), "%*s %*s %*s %f %f %f %f", &t0, &t1, &t2, &t3); //eg " <!-- Working space 10 10 410 287 -->"
|
||||
//coverity 151677
|
||||
blockDimensions = QRectF(t0, t1, t2 - t0, t3 - t1);
|
||||
}
|
||||
// Replace the editable text spans with new nodes holding actual values
|
||||
std::map<std::string, std::string>::iterator item =
|
||||
substitutions.find(tspanTempl.parentNode().toElement()
|
||||
.attribute(QString::fromUtf8("freecad:editable")).toStdString());
|
||||
if (item != substitutions.end()) {
|
||||
QDomElement tspanActual = templateDocument.createElement(QString::fromUtf8("tspan"));
|
||||
tspanActual.appendChild(templateDocument.createTextNode(QString::fromUtf8(item->second.c_str())));
|
||||
|
||||
}
|
||||
inTemplate.close();
|
||||
// Keep all spaces in the text node
|
||||
tspanActual.setAttribute(QString::fromUtf8("xml:space"), QString::fromUtf8("preserve"));
|
||||
|
||||
string outfragment(copyTemplate.str());
|
||||
std::string newfragment = outfragment;
|
||||
|
||||
// update EditableText SVG clauses with Property values
|
||||
std::map<std::string, std::string> subs = EditableTexts.getValues();
|
||||
|
||||
if (subs.size() > 0) {
|
||||
boost::regex e1 ("<text.*?freecad:editable=\"(.*?)\".*?<tspan.*?>(.*?)</tspan>");
|
||||
string::const_iterator begin, end;
|
||||
begin = outfragment.begin();
|
||||
end = outfragment.end();
|
||||
boost::match_results<std::string::const_iterator> what;
|
||||
|
||||
// Find editable texts
|
||||
while (boost::regex_search(begin, end, what, e1)) { //search in outfragment
|
||||
// if we have a replacement value for the text we've found
|
||||
if (subs.count(what[1].str())) {
|
||||
// change it to specified value
|
||||
boost::regex e2 ("(<text.*?freecad:editable=\"" + what[1].str() + "\".*?<tspan.*?)>(.*?)(</tspan>)");
|
||||
newfragment = boost::regex_replace(newfragment, e2, "$1>" + subs[what[1].str()] + "$3"); //replace in newfragment
|
||||
}
|
||||
begin = what[0].second;
|
||||
tspanTempl.parentNode().replaceChild(tspanActual, tspanTempl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// restoring linebreaks and saving the file
|
||||
boost::regex e3 ("--endOfLine--");
|
||||
string fmt = "\\n";
|
||||
outfragment = boost::regex_replace(newfragment, e3, fmt);
|
||||
|
||||
const QString qsOut = QString::fromStdString(outfragment);
|
||||
QDomDocument doc(QString::fromLatin1("mydocument"));
|
||||
|
||||
//if (!doc.setContent(&resultFile)) {
|
||||
if (!doc.setContent(qsOut)) {
|
||||
//setError(); //???? how/when does this get reset?
|
||||
std::string errMsg = std::string("Invalid SVG syntax in ") + getNameInDocument() + std::string(" - Check EditableTexts");
|
||||
return new App::DocumentObjectExecReturn(errMsg);
|
||||
} else {
|
||||
// make a temp file for FileIncluded Property
|
||||
string tempName = PageResult.getExchangeTempFile();
|
||||
ofstream outfinal(tempName.c_str());
|
||||
outfinal << outfragment;
|
||||
outfinal.close();
|
||||
PageResult.setValue(tempName.c_str());
|
||||
string pageResultFilename = PageResult.getExchangeTempFile();
|
||||
QFile pageResult(QString::fromUtf8(pageResultFilename.c_str()));
|
||||
if (pageResult.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QTextStream stream(&pageResult);
|
||||
stream << templateDocument.toString();
|
||||
pageResult.close();
|
||||
PageResult.setValue(pageResultFilename.c_str());
|
||||
}
|
||||
else {
|
||||
Base::Console().Message("DrawPage::execute() - failed to open file for writing: %s\n",
|
||||
pageResultFilename.c_str());
|
||||
}
|
||||
|
||||
// Calculate the dimensions of the page and store for retrieval
|
||||
// Parse the document XML
|
||||
QDomElement docElem = doc.documentElement();
|
||||
|
||||
// Obtain the size of the SVG document by reading the document attributes
|
||||
QDomElement docElement = templateDocument.documentElement();
|
||||
Base::Quantity quantity;
|
||||
|
||||
// Obtain the width
|
||||
QString str = docElem.attribute(QString::fromLatin1("width"));
|
||||
QString str = docElement.attribute(QString::fromLatin1("width"));
|
||||
quantity = Base::Quantity::parse(str);
|
||||
quantity.setUnit(Base::Unit::Length);
|
||||
|
||||
Width.setValue(quantity.getValue());
|
||||
|
||||
str = docElem.attribute(QString::fromLatin1("height"));
|
||||
str = docElement.attribute(QString::fromLatin1("height"));
|
||||
quantity = Base::Quantity::parse(str);
|
||||
quantity.setUnit(Base::Unit::Length);
|
||||
|
||||
@@ -250,14 +236,6 @@ App::DocumentObjectExecReturn * DrawSVGTemplate::execute(void)
|
||||
return TechDraw::DrawTemplate::execute();
|
||||
}
|
||||
|
||||
void DrawSVGTemplate::getBlockDimensions(double &x, double &y, double &width, double &height) const
|
||||
{
|
||||
x = blockDimensions.left();
|
||||
y = blockDimensions.bottom();
|
||||
width = blockDimensions.width();
|
||||
height = blockDimensions.height();
|
||||
}
|
||||
|
||||
double DrawSVGTemplate::getWidth() const
|
||||
{
|
||||
return Width.getValue();
|
||||
@@ -268,58 +246,63 @@ double DrawSVGTemplate::getHeight() const
|
||||
return Height.getValue();
|
||||
}
|
||||
|
||||
|
||||
std::map<std::string, std::string> DrawSVGTemplate::getEditableTextsFromTemplate()
|
||||
{
|
||||
std::map<std::string, std::string> eds;
|
||||
std::map<std::string, std::string> editables;
|
||||
|
||||
std::string temp = Template.getValue();
|
||||
if (!temp.empty()) {
|
||||
Base::FileInfo tfi(temp);
|
||||
std::string templateFilename = Template.getValue();
|
||||
if (templateFilename.empty()) {
|
||||
return editables;
|
||||
}
|
||||
|
||||
Base::FileInfo tfi(templateFilename);
|
||||
if (!tfi.isReadable()) {
|
||||
// if there is a old absolute template file set use a redirect
|
||||
tfi.setFile(App::Application::getResourceDir() + "Mod/Drawing/Templates/" + tfi.fileName());
|
||||
// try the redirect
|
||||
if (!tfi.isReadable()) {
|
||||
// if there is a old absolute template file set use a redirect
|
||||
tfi.setFile(App::Application::getResourceDir() + "Mod/Drawing/Templates/" + tfi.fileName());
|
||||
// try the redirect
|
||||
if (!tfi.isReadable()) {
|
||||
return eds;
|
||||
}
|
||||
}
|
||||
string tline, tfrag;
|
||||
ifstream tfile (tfi.filePath().c_str());
|
||||
while (getline (tfile,tline)) {
|
||||
tfrag += tline;
|
||||
tfrag += "--endOfLine--";
|
||||
}
|
||||
tfile.close();
|
||||
//this catches all the tags: <text ... </tspan></text>
|
||||
//keep tagRegex in sync with Gui/QGISVGTemplate.cpp
|
||||
boost::regex tagRegex ("<text([^>]*freecad:editable=[^>]*)>[^<]*<tspan[^>]*>([^<]*)</tspan>");
|
||||
boost::regex nameRegex("freecad:editable=\"(.*?)\"");
|
||||
boost::regex valueRegex("<tspan.*?>(.*?)</tspan>");
|
||||
|
||||
string::const_iterator tbegin, tend;
|
||||
tbegin = tfrag.begin();
|
||||
tend = tfrag.end();
|
||||
boost::match_results<std::string::const_iterator> tagMatch;
|
||||
boost::match_results<std::string::const_iterator> nameMatch;
|
||||
boost::match_results<std::string::const_iterator> valueMatch;
|
||||
while (boost::regex_search(tbegin, tend, tagMatch, tagRegex)) {
|
||||
if ( boost::regex_search(tagMatch[0].first, tagMatch[0].second, nameMatch, nameRegex) &&
|
||||
boost::regex_search(tagMatch[0].first, tagMatch[0].second, valueMatch, valueRegex)) {
|
||||
//found valid name/value pair
|
||||
string name = nameMatch[1];
|
||||
string value = valueMatch[1];
|
||||
if (eds.count(name) > 0) {
|
||||
//TODO: Throw or [better] change key
|
||||
qDebug() << "Got duplicate value for key "<<name.c_str();
|
||||
} else {
|
||||
eds[name] = value;
|
||||
}
|
||||
}
|
||||
tbegin = tagMatch[0].second;
|
||||
Base::Console().Log("DrawPage::getEditableTextsFromTemplate() not able to open %s!\n", Template.getValue());
|
||||
return editables;
|
||||
}
|
||||
}
|
||||
return eds;
|
||||
|
||||
QFile templateFile(QString::fromUtf8(tfi.filePath().c_str()));
|
||||
if (!templateFile.open(QIODevice::ReadOnly)) {
|
||||
Base::Console().Log("DrawPage::getEditableTextsFromTemplate() can't read template %s!\n", Template.getValue());
|
||||
return editables;
|
||||
}
|
||||
|
||||
QDomDocument templateDocument;
|
||||
if (!templateDocument.setContent(&templateFile)) {
|
||||
Base::Console().Message("DrawPage::getEditableTextsFromTemplate() - failed to parse file: %s\n",
|
||||
Template.getValue());
|
||||
return editables;
|
||||
}
|
||||
|
||||
QXmlQuery query(QXmlQuery::XQuery10);
|
||||
QDomNodeModel model(query.namePool(), templateDocument, true);
|
||||
query.setFocus(QXmlItem(model.fromDomNode(templateDocument.documentElement())));
|
||||
|
||||
// XPath query to select all <tspan> nodes whose <text> parent
|
||||
// has "freecad:editable" attribute
|
||||
query.setQuery(QString::fromUtf8(
|
||||
"declare default element namespace \"" SVG_NS_URI "\"; "
|
||||
"declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; "
|
||||
"//text[@freecad:editable]/tspan"));
|
||||
|
||||
QXmlResultItems queryResult;
|
||||
query.evaluateTo(&queryResult);
|
||||
|
||||
while (!queryResult.next().isNull())
|
||||
{
|
||||
QDomElement tspan = model.toDomNode(queryResult.current().toNodeModelIndex()).toElement();
|
||||
|
||||
// Takeover the names stored as attributes and the values stored as text items
|
||||
editables[tspan.parentNode().toElement().attribute(QString::fromUtf8("freecad:editable"))
|
||||
.toStdString()] = tspan.firstChild().nodeValue().toStdString();
|
||||
}
|
||||
|
||||
return editables;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
#include <QRectF>
|
||||
#include "DrawTemplate.h"
|
||||
|
||||
#define SVG_NS_URI "http://www.w3.org/2000/svg"
|
||||
#define FREECAD_SVG_NS_URI "http://www.freecadweb.org/wiki/index.php?title=Svg_Namespace"
|
||||
|
||||
namespace TechDraw
|
||||
{
|
||||
|
||||
@@ -61,7 +64,6 @@ public:
|
||||
|
||||
double getWidth() const;
|
||||
double getHeight() const;
|
||||
void getBlockDimensions(double &x, double &y, double &width, double &height) const;
|
||||
|
||||
protected:
|
||||
void onChanged(const App::Property* prop);
|
||||
@@ -71,8 +73,6 @@ protected:
|
||||
* Also populates editableSvgIds
|
||||
*/
|
||||
std::map<std::string, std::string> getEditableTextsFromTemplate();
|
||||
|
||||
QRectF blockDimensions;
|
||||
};
|
||||
|
||||
typedef App::FeaturePythonT<DrawSVGTemplate> DrawSVGTemplatePython;
|
||||
|
||||
@@ -126,11 +126,6 @@ App::DocumentObjectExecReturn *DrawTemplate::execute(void)
|
||||
return App::DocumentObject::execute();
|
||||
}
|
||||
|
||||
void DrawTemplate::getBlockDimensions(double & /*x*/, double & /*y*/, double & /*width*/, double & /*height*/) const
|
||||
{
|
||||
throw Base::NotImplementedError("implement in virtual function");
|
||||
}
|
||||
|
||||
DrawPage* DrawTemplate::getParentPage() const
|
||||
{
|
||||
TechDraw::DrawPage* page = nullptr;
|
||||
|
||||
@@ -57,7 +57,6 @@ public:
|
||||
/// Returns template height in mm
|
||||
virtual double getHeight() const;
|
||||
|
||||
virtual void getBlockDimensions(double &x, double &y, double &width, double &height) const;
|
||||
virtual DrawPage* getParentPage() const;
|
||||
|
||||
/** @name methods override Feature */
|
||||
|
||||
345
src/Mod/TechDraw/App/QDomNodeModel.cpp
Normal file
345
src/Mod/TechDraw/App/QDomNodeModel.cpp
Normal file
@@ -0,0 +1,345 @@
|
||||
// Copyright (c) 2011 Stanislaw Adaszewski, portions (c) 2019 Tomas Pavlicek
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// * Neither the name of Stanislaw Adaszewski nor the
|
||||
// names of other contributors may be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL STANISLAW ADASZEWSKI BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
#include "QDomNodeModel.h"
|
||||
|
||||
#include <QDomNode>
|
||||
#include <QDomDocument>
|
||||
#include <QUrl>
|
||||
#include <QVector>
|
||||
#include <QSourceLocation>
|
||||
#include <QVariant>
|
||||
|
||||
class PrivateDomNodeWrapper: public QDomNode
|
||||
{
|
||||
public:
|
||||
PrivateDomNodeWrapper(const QDomNode& other):
|
||||
QDomNode(other)
|
||||
{
|
||||
}
|
||||
|
||||
PrivateDomNodeWrapper(QDomNodePrivate *otherImpl):
|
||||
QDomNode(otherImpl)
|
||||
{
|
||||
}
|
||||
|
||||
QDomNodePrivate* getImpl()
|
||||
{
|
||||
return impl;
|
||||
}
|
||||
};
|
||||
|
||||
QDomNodeModel::QDomNodeModel(QXmlNamePool pool, QDomDocument doc, bool parsedReadOnly):
|
||||
m_Pool(pool), m_Doc(doc), m_ReadOnly(parsedReadOnly)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QUrl QDomNodeModel::baseUri (const QXmlNodeModelIndex &) const
|
||||
{
|
||||
// TODO: Not implemented.
|
||||
return QUrl();
|
||||
}
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
QXmlNodeModelIndex::DocumentOrder QDomNodeModel::compareOrder (
|
||||
const QXmlNodeModelIndex & ni1,
|
||||
const QXmlNodeModelIndex & ni2 ) const
|
||||
{
|
||||
QDomNode n1 = toDomNode(ni1);
|
||||
QDomNode n2 = toDomNode(ni2);
|
||||
|
||||
if (n1 == n2)
|
||||
return QXmlNodeModelIndex::Is;
|
||||
|
||||
if (m_ReadOnly)
|
||||
{
|
||||
int i1 = n1.lineNumber();
|
||||
int i2 = n2.lineNumber();
|
||||
|
||||
if (i1 < i2)
|
||||
return QXmlNodeModelIndex::Precedes;
|
||||
|
||||
if (i1 > i2)
|
||||
return QXmlNodeModelIndex::Follows;
|
||||
|
||||
i1 = n1.columnNumber();
|
||||
i2 = n2.columnNumber();
|
||||
|
||||
if (i1 < i2)
|
||||
return QXmlNodeModelIndex::Precedes;
|
||||
|
||||
if (i1 > i2)
|
||||
return QXmlNodeModelIndex::Follows;
|
||||
|
||||
return QXmlNodeModelIndex::Is;
|
||||
}
|
||||
|
||||
QVector<QDomNode> p1(path(n1));
|
||||
QVector<QDomNode> p2(path(n2));
|
||||
|
||||
if (p1.at(0) != p2.at(0))
|
||||
return QXmlNodeModelIndex::Is; // When root is not common, return obvious nonsense
|
||||
|
||||
int s = p1.size() < p2.size() ? p1.size() : p2.size();
|
||||
for (int i = 1; i < s; ++i)
|
||||
{
|
||||
if (p1.at(i) != p2.at(i))
|
||||
{
|
||||
QDomNode c = p1.at(i - 1).firstChild();
|
||||
while (!c.isNull())
|
||||
{
|
||||
if (c == p1.at(i))
|
||||
return QXmlNodeModelIndex::Precedes;
|
||||
if (c == p2.at(i))
|
||||
return QXmlNodeModelIndex::Follows;
|
||||
|
||||
c = c.nextSibling();
|
||||
}
|
||||
|
||||
return QXmlNodeModelIndex::Is; // Should be impossible!
|
||||
}
|
||||
}
|
||||
|
||||
return QXmlNodeModelIndex::Is; // Should be impossible!
|
||||
}
|
||||
|
||||
QUrl QDomNodeModel::documentUri (const QXmlNodeModelIndex&) const
|
||||
{
|
||||
// TODO: Not implemented.
|
||||
return QUrl();
|
||||
}
|
||||
|
||||
QXmlNodeModelIndex QDomNodeModel::elementById ( const QXmlName & id ) const
|
||||
{
|
||||
return fromDomNode(m_Doc.elementById(id.toClarkName(m_Pool)));
|
||||
}
|
||||
|
||||
QXmlNodeModelIndex::NodeKind QDomNodeModel::kind ( const QXmlNodeModelIndex & ni ) const
|
||||
{
|
||||
QDomNode n = toDomNode(ni);
|
||||
if (n.isAttr())
|
||||
return QXmlNodeModelIndex::Attribute;
|
||||
else if (n.isText())
|
||||
return QXmlNodeModelIndex::Text;
|
||||
else if (n.isComment())
|
||||
return QXmlNodeModelIndex::Comment;
|
||||
else if (n.isDocument())
|
||||
return QXmlNodeModelIndex::Document;
|
||||
else if (n.isElement())
|
||||
return QXmlNodeModelIndex::Element;
|
||||
else if (n.isProcessingInstruction())
|
||||
return QXmlNodeModelIndex::ProcessingInstruction;
|
||||
|
||||
return (QXmlNodeModelIndex::NodeKind) 0;
|
||||
}
|
||||
|
||||
QXmlName QDomNodeModel::name ( const QXmlNodeModelIndex & ni ) const
|
||||
{
|
||||
QDomNode n = toDomNode(ni);
|
||||
|
||||
if (n.isAttr() || n.isElement()) {
|
||||
if (!n.namespaceURI().isEmpty())
|
||||
return QXmlName(m_Pool, n.localName(), n.namespaceURI(), n.prefix());
|
||||
|
||||
QString p = n.prefix();
|
||||
QString t = n.nodeName();
|
||||
|
||||
if (p.isEmpty())
|
||||
{
|
||||
int c = t.indexOf(QLatin1Char(':'));
|
||||
if (c < 0)
|
||||
p = QString::fromUtf8("");
|
||||
else
|
||||
{
|
||||
p = t.left(c);
|
||||
t = t.mid(c + 1);
|
||||
}
|
||||
}
|
||||
|
||||
QVector<QXmlName> ns(namespaceBindings(ni));
|
||||
int x;
|
||||
for (x = 0; x < ns.size(); ++x)
|
||||
if (ns.at(x).prefix(m_Pool) == p) break;
|
||||
|
||||
if (x < ns.size())
|
||||
return QXmlName(m_Pool, t, ns.at(x).namespaceUri(m_Pool), p);
|
||||
}
|
||||
|
||||
return QXmlName(m_Pool, n.nodeName(), QString(), QString());
|
||||
}
|
||||
|
||||
QVector<QXmlName> QDomNodeModel::namespaceBindings(const QXmlNodeModelIndex & ni ) const
|
||||
{
|
||||
QDomNode n = toDomNode(ni);
|
||||
|
||||
QVector<QXmlName> res;
|
||||
while (!n.isNull())
|
||||
{
|
||||
QDomNamedNodeMap attrs = n.attributes();
|
||||
for (int i = 0; i < attrs.size(); ++i)
|
||||
{
|
||||
QString a = attrs.item(i).nodeName();
|
||||
|
||||
QString p;
|
||||
if (a == QString::fromUtf8("xmlns"))
|
||||
p = QString::fromUtf8("");
|
||||
else if (a.startsWith(QString::fromUtf8("xmlns:")))
|
||||
p = a.mid(6);
|
||||
|
||||
if (!p.isNull())
|
||||
{
|
||||
int x;
|
||||
for (x = 0; x < res.size(); ++x)
|
||||
if (res.at(x).prefix(m_Pool) == p) break;
|
||||
|
||||
if (x >= res.size())
|
||||
res.append(QXmlName(m_Pool, QString::fromUtf8("xmlns"), attrs.item(i).nodeValue(), p));
|
||||
}
|
||||
}
|
||||
|
||||
n = n.parentNode();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
QVector<QXmlNodeModelIndex> QDomNodeModel::nodesByIdref(const QXmlName&) const
|
||||
{
|
||||
// TODO: Not implemented.
|
||||
return QVector<QXmlNodeModelIndex>();
|
||||
}
|
||||
|
||||
QXmlNodeModelIndex QDomNodeModel::root ( const QXmlNodeModelIndex & ni ) const
|
||||
{
|
||||
QDomNode n = toDomNode(ni);
|
||||
while (!n.parentNode().isNull())
|
||||
n = n.parentNode();
|
||||
|
||||
return fromDomNode(n);
|
||||
}
|
||||
|
||||
QSourceLocation QDomNodeModel::sourceLocation(const QXmlNodeModelIndex&) const
|
||||
{
|
||||
// TODO: Not implemented.
|
||||
return QSourceLocation();
|
||||
}
|
||||
|
||||
QString QDomNodeModel::stringValue ( const QXmlNodeModelIndex & ni ) const
|
||||
{
|
||||
QDomNode n = toDomNode(ni);
|
||||
|
||||
if (n.isProcessingInstruction())
|
||||
return n.toProcessingInstruction().data();
|
||||
else if (n.isText())
|
||||
return n.toText().data();
|
||||
else if (n.isComment())
|
||||
return n.toComment().data();
|
||||
else if (n.isElement())
|
||||
return n.toElement().text();
|
||||
else if (n.isDocument())
|
||||
return n.toDocument().documentElement().text();
|
||||
else if (n.isAttr())
|
||||
return n.toAttr().value();
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
QVariant QDomNodeModel::typedValue ( const QXmlNodeModelIndex & ni ) const
|
||||
{
|
||||
return qVariantFromValue(stringValue(ni));
|
||||
}
|
||||
|
||||
QXmlNodeModelIndex QDomNodeModel::fromDomNode(const QDomNode &n) const
|
||||
{
|
||||
if (n.isNull())
|
||||
return QXmlNodeModelIndex();
|
||||
|
||||
return createIndex(PrivateDomNodeWrapper(n).getImpl(), 0);
|
||||
}
|
||||
|
||||
QDomNode QDomNodeModel::toDomNode(const QXmlNodeModelIndex &ni) const
|
||||
{
|
||||
return PrivateDomNodeWrapper((QDomNodePrivate*) ni.data());
|
||||
}
|
||||
|
||||
QVector<QDomNode> QDomNodeModel::path(const QDomNode &n) const
|
||||
{
|
||||
QVector<QDomNode> res;
|
||||
QDomNode cur = n;
|
||||
while (!cur.isNull())
|
||||
{
|
||||
res.push_back(cur);
|
||||
cur = cur.parentNode();
|
||||
}
|
||||
|
||||
std::reverse(res.begin(), res.end());
|
||||
return res;
|
||||
}
|
||||
|
||||
int QDomNodeModel::childIndex(const QDomNode &n) const
|
||||
{
|
||||
QDomNodeList children = n.parentNode().childNodes();
|
||||
for (int i = 0; i < children.size(); i++)
|
||||
if (children.at(i) == n)
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
QVector<QXmlNodeModelIndex> QDomNodeModel::attributes ( const QXmlNodeModelIndex & ni ) const
|
||||
{
|
||||
QDomElement n = toDomNode(ni).toElement();
|
||||
QDomNamedNodeMap attrs = n.attributes();
|
||||
QVector<QXmlNodeModelIndex> res;
|
||||
for (int i = 0; i < attrs.size(); i++)
|
||||
{
|
||||
res.push_back(fromDomNode(attrs.item(i)));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
QXmlNodeModelIndex QDomNodeModel::nextFromSimpleAxis ( SimpleAxis axis, const QXmlNodeModelIndex & ni) const
|
||||
{
|
||||
QDomNode n = toDomNode(ni);
|
||||
switch(axis)
|
||||
{
|
||||
case Parent:
|
||||
return fromDomNode(n.parentNode());
|
||||
|
||||
case FirstChild:
|
||||
return fromDomNode(n.firstChild());
|
||||
|
||||
case PreviousSibling:
|
||||
return fromDomNode(n.previousSibling());
|
||||
|
||||
case NextSibling:
|
||||
return fromDomNode(n.nextSibling());
|
||||
}
|
||||
|
||||
return QXmlNodeModelIndex();
|
||||
}
|
||||
41
src/Mod/TechDraw/App/QDomNodeModel.h
Normal file
41
src/Mod/TechDraw/App/QDomNodeModel.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#ifndef _QDOMNODEMODEL_H_
|
||||
#define _QDOMNODEMODEL_H_
|
||||
|
||||
#include <QAbstractXmlNodeModel>
|
||||
#include <QXmlNamePool>
|
||||
#include <QDomDocument>
|
||||
|
||||
class QDomNodeModel: public QAbstractXmlNodeModel
|
||||
{
|
||||
public:
|
||||
QDomNodeModel(QXmlNamePool, QDomDocument, bool parsedReadOnly = false);
|
||||
QUrl baseUri ( const QXmlNodeModelIndex & n ) const;
|
||||
QXmlNodeModelIndex::DocumentOrder compareOrder ( const QXmlNodeModelIndex & ni1, const QXmlNodeModelIndex & ni2 ) const;
|
||||
QUrl documentUri ( const QXmlNodeModelIndex & n ) const;
|
||||
QXmlNodeModelIndex elementById ( const QXmlName & id ) const;
|
||||
QXmlNodeModelIndex::NodeKind kind ( const QXmlNodeModelIndex & ni ) const;
|
||||
QXmlName name ( const QXmlNodeModelIndex & ni ) const;
|
||||
QVector<QXmlName> namespaceBindings ( const QXmlNodeModelIndex & n ) const;
|
||||
QVector<QXmlNodeModelIndex> nodesByIdref ( const QXmlName & idref ) const;
|
||||
QXmlNodeModelIndex root ( const QXmlNodeModelIndex & n ) const;
|
||||
QSourceLocation sourceLocation ( const QXmlNodeModelIndex & index ) const;
|
||||
QString stringValue ( const QXmlNodeModelIndex & n ) const;
|
||||
QVariant typedValue ( const QXmlNodeModelIndex & node ) const;
|
||||
|
||||
public:
|
||||
QXmlNodeModelIndex fromDomNode (const QDomNode&) const;
|
||||
QDomNode toDomNode(const QXmlNodeModelIndex &) const;
|
||||
QVector<QDomNode> path(const QDomNode&) const;
|
||||
int childIndex(const QDomNode&) const;
|
||||
|
||||
protected:
|
||||
QVector<QXmlNodeModelIndex> attributes ( const QXmlNodeModelIndex & element ) const;
|
||||
QXmlNodeModelIndex nextFromSimpleAxis ( SimpleAxis axis, const QXmlNodeModelIndex & origin) const;
|
||||
|
||||
private:
|
||||
mutable QXmlNamePool m_Pool;
|
||||
mutable QDomDocument m_Doc;
|
||||
bool m_ReadOnly;
|
||||
};
|
||||
|
||||
#endif // _QDOMNODEMODEL_H_
|
||||
@@ -15,6 +15,7 @@ include_directories(
|
||||
${OCC_INCLUDE_DIR}
|
||||
${ZLIB_INCLUDE_DIR}
|
||||
${XercesC_INCLUDE_DIRS}
|
||||
${QT_QTXMLPATTERNS_INCLUDE_DIR}
|
||||
)
|
||||
link_directories(${OCC_LIBRARY_DIR})
|
||||
|
||||
|
||||
@@ -43,6 +43,9 @@
|
||||
#include <cmath>
|
||||
#endif
|
||||
|
||||
#include <QXmlQuery>
|
||||
#include <QXmlResultItems>
|
||||
|
||||
#include <App/Application.h>
|
||||
#include <App/Document.h>
|
||||
#include <App/Material.h>
|
||||
@@ -72,6 +75,7 @@
|
||||
#include <Mod/TechDraw/App/DrawViewImage.h>
|
||||
#include <Mod/TechDraw/App/DrawLeaderLine.h>
|
||||
#include <Mod/TechDraw/App/DrawRichAnno.h>
|
||||
#include <Mod/TechDraw/App/QDomNodeModel.h>
|
||||
|
||||
#include "Rez.h"
|
||||
#include "QGIDrawingTemplate.h"
|
||||
@@ -740,16 +744,20 @@ void QGVPage::saveSvg(QString filename)
|
||||
docName;
|
||||
|
||||
QSvgGenerator svgGen;
|
||||
QTemporaryFile* tempFile = new QTemporaryFile();;
|
||||
svgGen.setOutputDevice(tempFile);
|
||||
svgGen.setSize(QSize((int) Rez::guiX(page->getPageWidth()), (int) Rez::guiX(page->getPageHeight()))); //expects pixels, gets mm
|
||||
QTemporaryFile temporaryFile;
|
||||
svgGen.setOutputDevice(&temporaryFile);
|
||||
|
||||
// Set resolution in DPI. Use the actual one, i.e. Rez::guiX(inch)
|
||||
svgGen.setResolution(Rez::guiX(25.4));
|
||||
|
||||
// Set size in pixels, which Qt recomputes using DPI to mm.
|
||||
int pixelWidth = Rez::guiX(page->getPageWidth());
|
||||
int pixelHeight = Rez::guiX(page->getPageHeight());
|
||||
svgGen.setSize(QSize(pixelWidth, pixelHeight));
|
||||
|
||||
//"By default this property is set to QSize(-1, -1), which indicates that the generator should not output
|
||||
// the width and height attributes of the <svg> element." >> but Inkscape won't read it without size info??
|
||||
svgGen.setViewBox(QRect(0, 0, Rez::guiX(page->getPageWidth()), Rez::guiX(page->getPageHeight())));
|
||||
|
||||
// Set resolution in DPI. To keep text dimensions as they are on screen,
|
||||
// use the very same resolution the screen paint device reports.
|
||||
svgGen.setResolution(MDIViewPage::getFromScene(scene())->logicalDpiY());
|
||||
svgGen.setViewBox(QRect(0, 0, pixelWidth, pixelHeight));
|
||||
|
||||
svgGen.setTitle(QObject::tr("FreeCAD SVG Export"));
|
||||
svgGen.setDescription(svgDescription);
|
||||
@@ -760,6 +768,17 @@ void QGVPage::saveSvg(QString filename)
|
||||
m_vpPage->setFrameState(false);
|
||||
m_vpPage->setTemplateMarkers(false);
|
||||
toggleHatch(false);
|
||||
|
||||
// Here we temporarily hide the page template, because Qt would otherwise convert the SVG template
|
||||
// texts into series of paths, making the later document edits practically unfeasible.
|
||||
// We will insert the SVG template ourselves in the final XML postprocessing operation.
|
||||
QGISVGTemplate *svgTemplate = dynamic_cast<QGISVGTemplate *>(pageTemplate);
|
||||
bool templateVisible = false;
|
||||
if (svgTemplate) {
|
||||
templateVisible = svgTemplate->isVisible();
|
||||
svgTemplate->hide();
|
||||
}
|
||||
|
||||
refreshViews();
|
||||
viewport()->repaint();
|
||||
|
||||
@@ -778,56 +797,119 @@ void QGVPage::saveSvg(QString filename)
|
||||
m_vpPage->setFrameState(saveState);
|
||||
m_vpPage->setTemplateMarkers(saveState);
|
||||
toggleHatch(true);
|
||||
if (templateVisible) {
|
||||
svgTemplate->show();
|
||||
}
|
||||
|
||||
refreshViews();
|
||||
viewport()->repaint();
|
||||
|
||||
tempFile->close();
|
||||
postProcessXml(tempFile, filename, pageName);
|
||||
temporaryFile.close();
|
||||
postProcessXml(temporaryFile, filename, pageName);
|
||||
}
|
||||
|
||||
void QGVPage::postProcessXml(QTemporaryFile* tempFile, QString fileName, QString pageName)
|
||||
void QGVPage::postProcessXml(QTemporaryFile& temporaryFile, QString fileName, QString pageName)
|
||||
{
|
||||
QDomDocument doc(QString::fromUtf8("SvgDoc"));
|
||||
QFile file(tempFile->fileName());
|
||||
QDomDocument exportDoc(QString::fromUtf8("SvgDoc"));
|
||||
QFile file(temporaryFile.fileName());
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
Base::Console().Message("QGVPage::ppsvg - tempfile open error\n");
|
||||
return;
|
||||
}
|
||||
if (!doc.setContent(&file)) {
|
||||
if (!exportDoc.setContent(&file)) {
|
||||
Base::Console().Message("QGVPage::ppsvg - xml error\n");
|
||||
file.close();
|
||||
return;
|
||||
}
|
||||
file.close();
|
||||
|
||||
QDomElement docElem = doc.documentElement(); //root <svg>
|
||||
QDomElement exportDocElem = exportDoc.documentElement(); //root <svg>
|
||||
|
||||
QDomNode n = docElem.firstChild();
|
||||
bool firstGroupFound = false;
|
||||
QString groupTag = QString::fromUtf8("g");
|
||||
QDomElement e;
|
||||
while(!n.isNull()) {
|
||||
e = n.toElement(); // try to convert the node to an element.
|
||||
if(!e.isNull()) {
|
||||
if (!firstGroupFound) {
|
||||
if (e.tagName() == groupTag) {
|
||||
firstGroupFound = true;
|
||||
break;
|
||||
QXmlQuery query(QXmlQuery::XQuery10);
|
||||
QDomNodeModel model(query.namePool(), exportDoc);
|
||||
query.setFocus(QXmlItem(model.fromDomNode(exportDocElem)));
|
||||
|
||||
// XPath query to select first <g> node as direct <svg> element descendant
|
||||
query.setQuery(QString::fromUtf8(
|
||||
"declare default element namespace \"" SVG_NS_URI "\"; "
|
||||
"declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; "
|
||||
"/svg/g[1]"));
|
||||
|
||||
QXmlResultItems queryResult;
|
||||
query.evaluateTo(&queryResult);
|
||||
|
||||
// Insert Freecad SVG namespace into namespace declarations
|
||||
exportDocElem.setAttribute(QString::fromUtf8("xmlns:freecad"),
|
||||
QString::fromUtf8(FREECAD_SVG_NS_URI));
|
||||
|
||||
// Set the first group's id to page name
|
||||
QDomElement g;
|
||||
if (!queryResult.next().isNull()) {
|
||||
g = model.toDomNode(queryResult.current().toNodeModelIndex()).toElement();
|
||||
g.setAttribute(QString::fromUtf8("id"), pageName);
|
||||
}
|
||||
|
||||
// Now insert our template
|
||||
QGISVGTemplate *svgTemplate = dynamic_cast<QGISVGTemplate *>(pageTemplate);
|
||||
if (svgTemplate) {
|
||||
DrawSVGTemplate *drawTemplate = svgTemplate->getSVGTemplate();
|
||||
if (drawTemplate) {
|
||||
QFile templateResultFile(QString::fromUtf8(drawTemplate->PageResult.getValue()));
|
||||
if (templateResultFile.open(QIODevice::ReadOnly)) {
|
||||
QDomDocument templateResultDoc(QString::fromUtf8("SvgDoc"));
|
||||
if (templateResultDoc.setContent(&templateResultFile)) {
|
||||
QDomElement templateDocElem = templateResultDoc.documentElement();
|
||||
|
||||
// Insert the template into a new group with id set to template name
|
||||
QDomElement groupWrapper = exportDoc.createElement(QString::fromUtf8("g"));
|
||||
Base::FileInfo fi(drawTemplate->Template.getValue());
|
||||
groupWrapper.setAttribute(QString::fromUtf8("id"),
|
||||
QString::fromUtf8(fi.fileName().c_str()));
|
||||
groupWrapper.setAttribute(QString::fromUtf8("style"),
|
||||
QString::fromUtf8("stroke: none;"));
|
||||
|
||||
// Scale the template group correctly
|
||||
groupWrapper.setAttribute(QString::fromUtf8("transform"),
|
||||
QString().sprintf("scale(%f, %f)", Rez::guiX(1.0), Rez::guiX(1.0)));
|
||||
|
||||
// Finally, transfer all template document child nodes under the wrapper group
|
||||
while (!templateDocElem.firstChild().isNull()) {
|
||||
groupWrapper.appendChild(templateDocElem.firstChild());
|
||||
}
|
||||
|
||||
if (!g.isNull()) {
|
||||
g.insertBefore(groupWrapper, QDomNode());
|
||||
}
|
||||
else {
|
||||
exportDocElem.insertBefore(groupWrapper, QDomNode());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
n = n.nextSibling();
|
||||
}
|
||||
e.setAttribute(QString::fromUtf8("id"),pageName);
|
||||
|
||||
// As icing on the cake, get rid of the empty <g>'s Qt SVG generator painting inserts.
|
||||
// XPath query to select any <g> element anywhere with no child nodes whatsoever
|
||||
query.setQuery(QString::fromUtf8(
|
||||
"declare default element namespace \"" SVG_NS_URI "\"; "
|
||||
"declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; "
|
||||
"//g[not(*)]"));
|
||||
|
||||
query.evaluateTo(&queryResult);
|
||||
while (!queryResult.next().isNull()) {
|
||||
g = model.toDomNode(queryResult.current().toNodeModelIndex()).toElement();
|
||||
g.parentNode().removeChild(g);
|
||||
}
|
||||
|
||||
// Time to save our product
|
||||
QFile outFile( fileName );
|
||||
if( !outFile.open( QIODevice::WriteOnly | QIODevice::Text ) ) {
|
||||
Base::Console().Message("QGVP::ppxml - failed to open file for writing: %s\n",qPrintable(fileName) );
|
||||
}
|
||||
|
||||
QTextStream stream( &outFile );
|
||||
stream << doc.toString();
|
||||
stream << exportDoc.toString();
|
||||
outFile.close();
|
||||
delete tempFile;
|
||||
}
|
||||
|
||||
void QGVPage::paintEvent(QPaintEvent *event)
|
||||
|
||||
@@ -115,7 +115,7 @@ public:
|
||||
|
||||
/// Renders the page to SVG with filename.
|
||||
void saveSvg(QString filename);
|
||||
void postProcessXml(QTemporaryFile* tempFile, QString filename, QString pagename);
|
||||
void postProcessXml(QTemporaryFile& tempFile, QString filename, QString pagename);
|
||||
|
||||
/* int balloonIndex;*/
|
||||
|
||||
|
||||
Reference in New Issue
Block a user