TechDraw: SVG export refactoring - All XML modifications via DOM

This commit is contained in:
Tomas Pavlicek
2019-06-07 21:09:14 +02:00
committed by WandererFan
parent b28b22f08a
commit a562f6a129
10 changed files with 620 additions and 171 deletions

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 */

View 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();
}

View 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_

View File

@@ -15,6 +15,7 @@ include_directories(
${OCC_INCLUDE_DIR}
${ZLIB_INCLUDE_DIR}
${XercesC_INCLUDE_DIRS}
${QT_QTXMLPATTERNS_INCLUDE_DIR}
)
link_directories(${OCC_LIBRARY_DIR})

View File

@@ -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)

View File

@@ -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;*/