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 &currentSchema) 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