1 /************************************************************************
2 **
3 ** @file vabstractconverter.cpp
4 ** @author Roman Telezhynskyi <dismine(at)gmail.com>
5 ** @date 10 12, 2014
6 **
7 ** @brief
8 ** @copyright
9 ** This source code is part of the Valentina project, a pattern making
10 ** program, whose allow create and modeling patterns of clothing.
11 ** Copyright (C) 2013-2015 Valentina project
12 ** <https://gitlab.com/smart-pattern/valentina> All Rights Reserved.
13 **
14 ** Valentina is free software: you can redistribute it and/or modify
15 ** it under the terms of the GNU General Public License as published by
16 ** the Free Software Foundation, either version 3 of the License, or
17 ** (at your option) any later version.
18 **
19 ** Valentina is distributed in the hope that it will be useful,
20 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 ** GNU General Public License for more details.
23 **
24 ** You should have received a copy of the GNU General Public License
25 ** along with Valentina. If not, see <http://www.gnu.org/licenses/>.
26 **
27 *************************************************************************/
28
29 #include "vabstractconverter.h"
30
31 #include <QAbstractMessageHandler>
32 #include <QDir>
33 #include <QDomElement>
34 #include <QDomNode>
35 #include <QDomNodeList>
36 #include <QFile>
37 #include <QFileInfo>
38 #include <QLatin1String>
39 #include <QMap>
40 #include <QRegularExpression>
41 #include <QRegularExpressionMatch>
42 #include <QSourceLocation>
43 #include <QStaticStringData>
44 #include <QStringData>
45 #include <QStringDataPtr>
46 #include <QStringList>
47 #include <QTextDocument>
48 #include <QUrl>
49 #include <QXmlSchema>
50 #include <QXmlSchemaValidator>
51
52 #include "../exception/vexception.h"
53 #include "../exception/vexceptionwrongid.h"
54 #include "vdomdocument.h"
55
56 //This class need for validation pattern file using XSD shema
57 class MessageHandler : public QAbstractMessageHandler
58 {
59 public:
MessageHandler()60 MessageHandler()
61 : QAbstractMessageHandler(),
62 m_messageType(QtMsgType()),
63 m_description(),
64 m_sourceLocation(QSourceLocation())
65 {}
66
67 QString statusMessage() const;
68 qint64 line() const;
69 qint64 column() const;
70 protected:
71 // cppcheck-suppress unusedFunction
72 virtual void handleMessage(QtMsgType type, const QString &description,
73 const QUrl &identifier, const QSourceLocation &sourceLocation) override;
74 private:
75 QtMsgType m_messageType;
76 QString m_description;
77 QSourceLocation m_sourceLocation;
78 };
79
80 //---------------------------------------------------------------------------------------------------------------------
statusMessage() const81 QString MessageHandler::statusMessage() const
82 {
83 QTextDocument doc;
84 doc.setHtml(m_description);
85 return doc.toPlainText();
86 }
87
88 //---------------------------------------------------------------------------------------------------------------------
line() const89 inline qint64 MessageHandler::line() const
90 {
91 return m_sourceLocation.line();
92 }
93
94 //---------------------------------------------------------------------------------------------------------------------
column() const95 inline qint64 MessageHandler::column() const
96 {
97 return m_sourceLocation.column();
98 }
99
100 //---------------------------------------------------------------------------------------------------------------------
101 // cppcheck-suppress unusedFunction
handleMessage(QtMsgType type,const QString & description,const QUrl & identifier,const QSourceLocation & sourceLocation)102 void MessageHandler::handleMessage(QtMsgType type, const QString &description, const QUrl &identifier,
103 const QSourceLocation &sourceLocation)
104 {
105 Q_UNUSED(type)
106 Q_UNUSED(identifier)
107
108 m_messageType = type;
109 m_description = description;
110 m_sourceLocation = sourceLocation;
111 }
112
113 //---------------------------------------------------------------------------------------------------------------------
VAbstractConverter(const QString & fileName)114 VAbstractConverter::VAbstractConverter(const QString &fileName)
115 : VDomDocument(),
116 m_ver(0x0),
117 m_originalFileName(fileName),
118 m_convertedFileName(fileName),
119 m_tmpFile()
120 {
121 setXMLContent(m_convertedFileName);// Throw an exception on error
122 m_ver = GetFormatVersion(GetFormatVersionStr());
123 }
124
125 //---------------------------------------------------------------------------------------------------------------------
Convert()126 QString VAbstractConverter::Convert()
127 {
128 if (m_ver == MaxVer())
129 {
130 return m_convertedFileName;
131 }
132
133 if (not IsReadOnly())
134 {
135 ReserveFile();
136 }
137
138 if (m_tmpFile.open())
139 {
140 m_convertedFileName = m_tmpFile.fileName();
141 }
142 else
143 {
144 throw VException(tr("Error openning a temp file: %1.").arg(m_tmpFile.errorString()));
145 }
146
147 m_ver < MaxVer() ? ApplyPatches() : DowngradeToCurrentMaxVersion();
148
149 return m_convertedFileName;
150 }
151
152 //---------------------------------------------------------------------------------------------------------------------
GetCurrentFormatVersion() const153 int VAbstractConverter::GetCurrentFormatVersion() const
154 {
155 return m_ver;
156 }
157
158 //---------------------------------------------------------------------------------------------------------------------
ReserveFile() const159 void VAbstractConverter::ReserveFile() const
160 {
161 //It's not possible in all cases make conversion without lose data.
162 //For such cases we will store old version in a reserve file.
163 QString error;
164 QFileInfo info(m_convertedFileName);
165 const QString reserveFileName = QString("%1/%2(v%3).%4.bak")
166 .arg(info.absoluteDir().absolutePath(), info.baseName(), GetFormatVersionStr(), info.completeSuffix());
167 if (not SafeCopy(m_convertedFileName, reserveFileName, error))
168 {
169 #ifdef Q_OS_WIN32
170 qt_ntfs_permission_lookup++; // turn checking on
171 #endif /*Q_OS_WIN32*/
172 const bool isFileWritable = info.isWritable();
173 #ifdef Q_OS_WIN32
174 qt_ntfs_permission_lookup--; // turn it off again
175 #endif /*Q_OS_WIN32*/
176 if (not IsReadOnly() && isFileWritable)
177 {
178 const QString errorMsg(tr("Error creating a reserv copy: %1.").arg(error));
179 throw VException(errorMsg);
180 }
181 }
182 }
183
184 //---------------------------------------------------------------------------------------------------------------------
Replace(QString & formula,const QString & newName,int position,const QString & token,int & bias) const185 void VAbstractConverter::Replace(QString &formula, const QString &newName, int position, const QString &token,
186 int &bias) const
187 {
188 formula.replace(position, token.length(), newName);
189 bias = token.length() - newName.length();
190 }
191
192 //---------------------------------------------------------------------------------------------------------------------
CorrectionsPositions(int position,int bias,QMap<int,QString> & tokens) const193 void VAbstractConverter::CorrectionsPositions(int position, int bias, QMap<int, QString> &tokens) const
194 {
195 if (bias == 0)
196 {
197 return;// Nothing to correct;
198 }
199
200 BiasTokens(position, bias, tokens);
201 }
202
203 //---------------------------------------------------------------------------------------------------------------------
BiasTokens(int position,int bias,QMap<int,QString> & tokens)204 void VAbstractConverter::BiasTokens(int position, int bias, QMap<int, QString> &tokens)
205 {
206 QMap<int, QString> newTokens;
207 QMap<int, QString>::const_iterator i = tokens.constBegin();
208 while (i != tokens.constEnd())
209 {
210 if (i.key()<= position)
211 { // Tokens before position "position" did not change his positions.
212 newTokens.insert(i.key(), i.value());
213 }
214 else
215 {
216 newTokens.insert(i.key()-bias, i.value());
217 }
218 ++i;
219 }
220 tokens = newTokens;
221 }
222
223 //---------------------------------------------------------------------------------------------------------------------
224 /**
225 * @brief ValidateXML validate xml file by xsd schema.
226 * @param schema path to schema file.
227 */
ValidateXML(const QString & schema) const228 void VAbstractConverter::ValidateXML(const QString &schema) const
229 {
230 qCDebug(vXML, "Validation xml file %s.", qUtf8Printable(m_convertedFileName));
231 QFile pattern(m_convertedFileName);
232 if (not pattern.open(QIODevice::ReadOnly))
233 {
234 const QString errorMsg(tr("Can't open file %1:\n%2.").arg(m_convertedFileName, pattern.errorString()));
235 throw VException(errorMsg);
236 }
237
238 QFile fileSchema(schema);
239 if (not fileSchema.open(QIODevice::ReadOnly))
240 {
241 pattern.close();
242 const QString errorMsg(tr("Can't open schema file %1:\n%2.").arg(schema, fileSchema.errorString()));
243 throw VException(errorMsg);
244 }
245
246 MessageHandler messageHandler;
247 QXmlSchema sch;
248 sch.setMessageHandler(&messageHandler);
249 if (sch.load(&fileSchema, QUrl::fromLocalFile(fileSchema.fileName()))==false)
250 {
251 pattern.close();
252 fileSchema.close();
253 VException e(messageHandler.statusMessage());
254 e.AddMoreInformation(tr("Could not load schema file '%1'.").arg(fileSchema.fileName()));
255 throw e;
256 }
257 qCDebug(vXML, "Schema loaded.");
258
259 bool errorOccurred = false;
260 if (sch.isValid() == false)
261 {
262 errorOccurred = true;
263 }
264 else
265 {
266 QXmlSchemaValidator validator(sch);
267 if (validator.validate(&pattern, QUrl::fromLocalFile(pattern.fileName())) == false)
268 {
269 errorOccurred = true;
270 }
271 }
272
273 if (errorOccurred)
274 {
275 pattern.close();
276 fileSchema.close();
277 VException e(messageHandler.statusMessage());
278 e.AddMoreInformation(tr("Validation error file %3 in line %1 column %2").arg(messageHandler.line())
279 .arg(messageHandler.column()).arg(m_originalFileName));
280 throw e;
281 }
282 pattern.close();
283 fileSchema.close();
284 }
285
286 //---------------------------------------------------------------------------------------------------------------------
InvalidVersion(int ver) const287 Q_NORETURN void VAbstractConverter::InvalidVersion(int ver) const
288 {
289 if (ver < MinVer())
290 {
291 const QString errorMsg(tr("Invalid version. Minimum supported format version is %1").arg(MinVerStr()));
292 throw VException(errorMsg);
293 }
294
295 if (ver > MaxVer())
296 {
297 const QString errorMsg(tr("Invalid version. Maximum supported format version is %1").arg(MaxVerStr()));
298 throw VException(errorMsg);
299 }
300
301 const QString errorMsg(tr("Unexpected version \"%1\".").arg(ver, 0, 16));
302 throw VException(errorMsg);
303 }
304
305 //---------------------------------------------------------------------------------------------------------------------
ValidateInputFile(const QString & currentSchema) const306 void VAbstractConverter::ValidateInputFile(const QString ¤tSchema) const
307 {
308 QString schema;
309 try
310 {
311 schema = XSDSchema(m_ver);
312 }
313 catch(const VException &e)
314 {
315 if (m_ver < MinVer())
316 { // Version less than minimally supported version. Can't do anything.
317 throw;
318 }
319 else if (m_ver > MaxVer())
320 { // Version bigger than maximum supported version. We still have a chance to open the file.
321 try
322 { // Try to open like the current version.
323 ValidateXML(currentSchema);
324 }
325 catch(const VException &exp)
326 { // Nope, we can't.
327 Q_UNUSED(exp)
328 throw e;
329 }
330 }
331 else
332 { // Unexpected version. Most time mean that we do not catch all versions between min and max.
333 throw;
334 }
335
336 return; // All is fine and we can try to convert to current max version.
337 }
338
339 ValidateXML(schema);
340 }
341
342 //---------------------------------------------------------------------------------------------------------------------
Save()343 void VAbstractConverter::Save()
344 {
345 try
346 {
347 TestUniqueId();
348 }
349 catch (const VExceptionWrongId &e)
350 {
351 Q_UNUSED(e)
352 VException ex(tr("Error no unique id."));
353 throw ex;
354 }
355
356 m_tmpFile.resize(0);//clear previous content
357 const int indent = 4;
358 QTextStream out(&m_tmpFile);
359 out.setCodec("UTF-8");
360 save(out, indent);
361
362 if (not m_tmpFile.flush())
363 {
364 VException e(m_tmpFile.errorString());
365 throw e;
366 }
367 }
368
369 //---------------------------------------------------------------------------------------------------------------------
SetVersion(const QString & version)370 void VAbstractConverter::SetVersion(const QString &version)
371 {
372 ValidateVersion(version);
373
374 if (setTagText(TagVersion, version) == false)
375 {
376 VException e(tr("Could not change version."));
377 throw e;
378 }
379 }
380