Seamly2D
Code documentation
vabstractconverter.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  ** @file vabstractconverter.cpp
3  ** @author Douglas S Caskey
4  ** @date Dec 27, 2022
5  **
6  ** @copyright
7  ** Copyright (C) 2017 - 2022 Seamly, LLC
8  ** https://github.com/fashionfreedom/seamly2d
9  **
10  ** @brief
11  ** Seamly2D is free software: you can redistribute it and/or modify
12  ** it under the terms of the GNU General Public License as published by
13  ** the Free Software Foundation, either version 3 of the License, or
14  ** (at your option) any later version.
15  **
16  ** Seamly2D is distributed in the hope that it will be useful,
17  ** but WITHOUT ANY WARRANTY; without even the implied warranty of
18  ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  ** GNU General Public License for more details.
20  **
21  ** You should have received a copy of the GNU General Public License
22  ** along with Seamly2D. If not, see <http://www.gnu.org/licenses/>.
23  **************************************************************************/
24 
25 /************************************************************************
26  **
27  ** @file vabstractconverter.cpp
28  ** @author Roman Telezhynskyi <dismine(at)gmail.com>
29  ** @date 10 12, 2014
30  **
31  ** @brief
32  ** @copyright
33  ** This source code is part of the Valentina project, a pattern making
34  ** program, whose allow create and modeling patterns of clothing.
35  ** Copyright (C) 2014 Valentina project
36  ** <https://bitbucket.org/dismine/valentina> All Rights Reserved.
37  **
38  ** Valentina is free software: you can redistribute it and/or modify
39  ** it under the terms of the GNU General Public License as published by
40  ** the Free Software Foundation, either version 3 of the License, or
41  ** (at your option) any later version.
42  **
43  ** Valentina is distributed in the hope that it will be useful,
44  ** but WITHOUT ANY WARRANTY; without even the implied warranty of
45  ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
46  ** GNU General Public License for more details.
47  **
48  ** You should have received a copy of the GNU General Public License
49  ** along with Seamly2D. If not, see <http://www.gnu.org/licenses/>.
50  **
51  *************************************************************************/
52 
53 #include "vabstractconverter.h"
54 
55 #include <QDir>
56 #include <QDomElement>
57 #include <QDomNode>
58 #include <QDomNodeList>
59 #include <QFile>
60 #include <QFileInfo>
61 #include <QLatin1String>
62 #include <QMap>
63 #include <QRegularExpression>
64 #include <QRegularExpressionMatch>
65 #include <QStaticStringData>
66 #include <QStringData>
67 #include <QStringDataPtr>
68 #include <QStringList>
69 
70 #include "../exception/vexception.h"
71 #include "../exception/vexceptionwrongid.h"
72 #include "vdomdocument.h"
73 
74 //---------------------------------------------------------------------------------------------------------------------
76  : VDomDocument(),
77  m_ver(0x0),
78  m_convertedFileName(fileName),
79  m_tmpFile()
80 {
81  setXMLContent(m_convertedFileName);// Throw an exception on error
83 
84  qDebug() << "VAbstractConverter::GetVersion() = " << m_ver;
85 }
86 
87 //---------------------------------------------------------------------------------------------------------------------
89 {
90  if (m_ver == MaxVer())
91  {
92  return m_convertedFileName;
93  }
94 
95  if (not IsReadOnly())
96  {
97  ReserveFile();
98  }
99 
100  if (m_tmpFile.open())
101  {
102  m_convertedFileName = m_tmpFile.fileName();
103  }
104  else
105  {
106  const QString errorMsg(tr("Error Opening a temp file: %1.").arg(m_tmpFile.errorString()));
107  throw VException(errorMsg);
108  }
109 
110  qDebug() << " m_ver = " << m_ver;
111  qDebug() << " MaxVer = " << MaxVer();
112 
114 
115  return m_convertedFileName;
116 }
117 
118 //---------------------------------------------------------------------------------------------------------------------
120 {
121  return m_ver;
122 }
123 
124 //---------------------------------------------------------------------------------------------------------------------
126 {
127  const QDomNodeList nodeList = this->elementsByTagName(TagVersion);
128  if (nodeList.isEmpty())
129  {
130  const QString errorMsg(tr("Couldn't get version information."));
131  throw VException(errorMsg);
132  }
133 
134  if (nodeList.count() > 1)
135  {
136  const QString errorMsg(tr("Too many tags <%1> in file.").arg(TagVersion));
137  throw VException(errorMsg);
138  }
139 
140  const QDomNode domNode = nodeList.at(0);
141  if (domNode.isNull() == false && domNode.isElement())
142  {
143  const QDomElement domElement = domNode.toElement();
144  if (domElement.isNull() == false)
145  {
146  qDebug() << " VAbstractConverter::GetVersionStr()" << domElement.text();
147  return domElement.text();
148  }
149  }
150  return QString(QStringLiteral("0.0.0"));
151 }
152 
153 //---------------------------------------------------------------------------------------------------------------------
154 int VAbstractConverter::GetVersion(const QString &version)
155 {
156  ValidateVersion(version);
157 
158  const QStringList ver = version.split(".");
159 
160  bool ok = false;
161  const int major = ver.at(0).toInt(&ok);
162  if (not ok)
163  {
164  return 0x0;
165  }
166 
167  ok = false;
168  const int minor = ver.at(1).toInt(&ok);
169  if (not ok)
170  {
171  return 0x0;
172  }
173 
174  ok = false;
175  const int patch = ver.at(2).toInt(&ok);
176  if (not ok)
177  {
178  return 0x0;
179  }
180  return (major<<16)|(minor<<8)|(patch);
181 }
182 
183 //---------------------------------------------------------------------------------------------------------------------
184 QString VAbstractConverter::removeVersionNumber(const QString& fileName)
185 {
186  // This method removes different instances of version numbers for the filename:
187  // 1. Regular, old format version: myPattern(v0.6.0).val
188  // 2. Messed up, "recursive" old format: myPattern(v0(v0.6.0).6.0).val.bak
189  // 3. New format: myPattern_v060.val
190  // For all the above cases the return value should be: myPattern.<ext>
191 
192  const QRegularExpression newVersionRx(QStringLiteral("_v\\d\\d\\d"));
193  QString fileNameWithoutVersion;
194  int dotPos = fileName.lastIndexOf(QLatin1Char('.'));
195  int versionPos = fileName.indexOf(QLatin1String("(v"));
196 
197  if (versionPos > 0)
198  {
199  // Old format version number should be removed until the last ')' if present
200  int lastParenthesisPos = fileName.lastIndexOf(QLatin1Char(')'));
201  if (lastParenthesisPos > versionPos && lastParenthesisPos < dotPos)
202  {
203  dotPos = lastParenthesisPos + 1;
204  }
205 
206  fileNameWithoutVersion = fileName.left(versionPos);
207  }
208  else if ((versionPos = fileName.indexOf(newVersionRx)) > -1)
209  {
210  // New format version number should be removed until the first '.' (if present)
211  dotPos = fileName.indexOf(QLatin1Char('.'), versionPos);
212  fileNameWithoutVersion = fileName.left(versionPos);
213  }
214  else
215  {
216  fileNameWithoutVersion = fileName;
217  }
218 
219  if (versionPos > -1 && dotPos > versionPos)
220  {
221  fileNameWithoutVersion += fileName.mid(dotPos);
222  }
223 
224  return fileNameWithoutVersion;
225 }
226 
227 //---------------------------------------------------------------------------------------------------------------------
228 QString VAbstractConverter::removeBakExtension(const QString &fileName)
229 {
230  QString newFileName = fileName;
231  int dotPos = newFileName.lastIndexOf(QLatin1Char('.'));
232 
233  while (dotPos > -1 && newFileName.mid(dotPos) == QLatin1String(".bak"))
234  {
235  newFileName = newFileName.left(dotPos);
236  dotPos = newFileName.lastIndexOf(QLatin1Char('.'));
237  }
238 
239  return newFileName;
240 }
241 
242 //---------------------------------------------------------------------------------------------------------------------
243 void VAbstractConverter::ValidateVersion(const QString &version)
244 {
245  const QRegularExpression rx(QStringLiteral("^(0|([1-9][0-9]*)).(0|([1-9][0-9]*)).(0|([1-9][0-9]*))$"));
246 
247  if (rx.match(version).hasMatch() == false)
248  {
249  const QString errorMsg(tr("Version \"%1\" invalid.").arg(version));
250  throw VException(errorMsg);
251  }
252 
253  if (version == QLatin1String("0.0.0"))
254  {
255  const QString errorMsg(tr("Version \"0.0.0\" invalid."));
256  throw VException(errorMsg);
257  }
258 }
259 
260 //---------------------------------------------------------------------------------------------------------------------
262 {
263  //It's not possible in all cases make conversion without lose data.
264  //For such cases we will store old version in a reserve file.
265  QString error;
267  const QString baseNameWithoutVersion = info.baseName();
268  const QString versionWithoutDots = GetVersionStr().remove(QLatin1Char('.'));
269  const QString baseFileName = QString("%1_v%2")
270  .arg(baseNameWithoutVersion)
271  .arg(versionWithoutDots);
272  QString sequencePart;
273  int sequenceNumber = 1;
274  QString reserveFileName;
275 
276  do {
277  reserveFileName = QString("%1/%2%3.%4")
278  .arg(info.absoluteDir().absolutePath())
279  .arg(baseFileName)
280  .arg(sequencePart)
281  .arg(info.completeSuffix());
282  sequencePart = QString("_(%1)").arg(++sequenceNumber);
283  } while (QFileInfo(reserveFileName).exists());
284 
285  if (not SafeCopy(m_convertedFileName, reserveFileName, error))
286  {
287 #ifdef Q_OS_WIN32
288  qt_ntfs_permission_lookup++; // turn checking on
289 #endif /*Q_OS_WIN32*/
290  const bool isFileWritable = info.isWritable();
291 #ifdef Q_OS_WIN32
292  qt_ntfs_permission_lookup--; // turn it off again
293 #endif /*Q_OS_WIN32*/
294 
295  if (not IsReadOnly() && isFileWritable)
296  {
297  const QString errorMsg(tr("Error creating a reserv copy: %1.").arg(error));
298  throw VException(errorMsg);
299  }
300  }
301 }
302 
303 //---------------------------------------------------------------------------------------------------------------------
304 void VAbstractConverter::Replace(QString &formula, const QString &newName, int position, const QString &token,
305  int &bias) const
306 {
307  formula.replace(position, token.length(), newName);
308  bias = token.length() - newName.length();
309 }
310 
311 //---------------------------------------------------------------------------------------------------------------------
312 void VAbstractConverter::CorrectionsPositions(int position, int bias, QMap<int, QString> &tokens) const
313 {
314  if (bias == 0)
315  {
316  return;// Nothing to correct;
317  }
318 
319  BiasTokens(position, bias, tokens);
320 }
321 
322 //---------------------------------------------------------------------------------------------------------------------
323 void VAbstractConverter::BiasTokens(int position, int bias, QMap<int, QString> &tokens)
324 {
325  QMap<int, QString> newTokens;
326  QMap<int, QString>::const_iterator i = tokens.constBegin();
327  while (i != tokens.constEnd())
328  {
329  if (i.key()<= position)
330  { // Tokens before position "position" did not change his positions.
331  newTokens.insert(i.key(), i.value());
332  }
333  else
334  {
335  newTokens.insert(i.key()-bias, i.value());
336  }
337  ++i;
338  }
339  tokens = newTokens;
340 }
341 
342 //---------------------------------------------------------------------------------------------------------------------
343 Q_NORETURN void VAbstractConverter::InvalidVersion(int ver) const
344 {
345  if (ver < MinVer())
346  {
347  const QString errorMsg(tr("Invalid version. Minimum supported version is %1").arg(MinVerStr()));
348  throw VException(errorMsg);
349  }
350 
351  if (ver > MaxVer())
352  {
353  const QString errorMsg(tr("Invalid version. Maximum supported version is %1").arg(MaxVerStr()));
354  throw VException(errorMsg);
355  }
356 
357  const QString errorMsg(tr("Unexpected version \"%1\".").arg(ver, 0, 16));
358  throw VException(errorMsg);
359 }
360 
361 //---------------------------------------------------------------------------------------------------------------------
362 void VAbstractConverter::ValidateInputFile(const QString &currentSchema) const
363 {
364  QString schema;
365  try
366  {
367  schema = XSDSchema(m_ver);
368  }
369  catch(const VException &e)
370  {
371  if (m_ver < MinVer())
372  { // Version less than minimally supported version. Can't do anything.
373  throw;
374  }
375  else if (m_ver > MaxVer())
376  { // Version bigger than maximum supported version. We still have a chance to open the file.
377  try
378  { // Try to open like the current version.
379  ValidateXML(currentSchema, m_convertedFileName);
380  }
381  catch(const VException &exp)
382  { // Nope, we can't.
383  Q_UNUSED(exp)
384  throw e;
385  }
386  }
387  else
388  { // Unexpected version. Most time mean that we do not catch all versions between min and max.
389  throw;
390  }
391 
392  return; // All is fine and we can try to convert to current max version.
393  }
394 
396 }
397 
398 //---------------------------------------------------------------------------------------------------------------------
400 {
401  try
402  {
403  TestUniqueId();
404  }
405  catch (const VExceptionWrongId &e)
406  {
407  Q_UNUSED(e)
408  VException ex(tr("Error no unique id."));
409  throw ex;
410  }
411 
412  m_tmpFile.resize(0);//clear previous content
413  const int indent = 4;
414  QTextStream out(&m_tmpFile);
415  out.setCodec("UTF-8");
416  save(out, indent);
417 
418  if (not m_tmpFile.flush())
419  {
420  VException e(m_tmpFile.errorString());
421  throw e;
422  }
423 }
424 
425 //---------------------------------------------------------------------------------------------------------------------
426 void VAbstractConverter::SetVersion(const QString &version)
427 {
428  ValidateVersion(version);
429 
430  if (setTagText(TagVersion, version) == false)
431  {
432  VException e(tr("Could not change version."));
433  throw e;
434  }
435 }
static int GetVersion(const QString &version)
static QString removeVersionNumber(const QString &fileName)
Removes version number from.
virtual void DowngradeToCurrentMaxVersion()=0
virtual bool IsReadOnly() const =0
void SetVersion(const QString &version)
void Replace(QString &formula, const QString &newName, int position, const QString &token, int &bias) const
virtual QString MaxVerStr() const =0
int GetCurrentFormatVarsion() const
static void BiasTokens(int position, int bias, QMap< int, QString > &tokens)
static void ValidateVersion(const QString &version)
Q_NORETURN void InvalidVersion(int ver) const
virtual QString XSDSchema(int ver) const =0
virtual int MaxVer() const =0
static QString removeBakExtension(const QString &fileName)
Removes single or repeated '.bak' extension (as long as it is at the end of.
void CorrectionsPositions(int position, int bias, QMap< int, QString > &tokens) const
virtual int MinVer() const =0
void ValidateInputFile(const QString &currentSchema) const
QString GetVersionStr() const
VAbstractConverter(const QString &fileName)
virtual QString MinVerStr() const =0
virtual void ApplyPatches()=0
QTemporaryFile m_tmpFile
The VDomDocument class represents a Seamly2D document (.val file).
Definition: vdomdocument.h:105
static void ValidateXML(const QString &schema, const QString &fileName)
ValidateXML validate xml file by xsd schema.
void TestUniqueId() const
TestUniqueId test exist unique id in pattern file. Each id must be unique.
bool setTagText(const QString &tag, const QString &text)
static bool SafeCopy(const QString &source, const QString &destination, QString &error)
static const QString TagVersion
Definition: vdomdocument.h:115
virtual void setXMLContent(const QString &fileName)
The VExceptionWrongId class for exception wrong id.
The VException class parent for all exception. Could be use for abstract exception.
Definition: vexception.h:66