1 /************************************************************************
2  **
3  **  @file   vdomdocument.cpp
4  **  @author Roman Telezhynskyi <dismine(at)gmail.com>
5  **  @date   November 15, 2013
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 "vdomdocument.h"
30 
31 #include <qcompilerdetection.h>
32 #include <qdom.h>
33 #include <QSaveFile>
34 
35 #include "../exception/vexceptionbadid.h"
36 #include "../exception/vexceptionconversionerror.h"
37 #include "../exception/vexceptionemptyparameter.h"
38 #include "../exception/vexceptionwrongid.h"
39 #include "../exception/vexception.h"
40 #include "../ifcdef.h"
41 
42 #include <QAbstractMessageHandler>
43 #include <QByteArray>
44 #include <QDomNodeList>
45 #include <QDomText>
46 #include <QFile>
47 #include <QIODevice>
48 #include <QMessageLogger>
49 #include <QObject>
50 #include <QSourceLocation>
51 #include <QStringList>
52 #include <QTemporaryFile>
53 #include <QTextDocument>
54 #include <QTextStream>
55 #include <QUrl>
56 #include <QVector>
57 #include <QtDebug>
58 #include <QXmlStreamWriter>
59 #include <QTimer>
60 #include <QtConcurrentRun>
61 #include <QFutureWatcher>
62 #include <QRegularExpression>
63 
64 namespace
65 {
66 //---------------------------------------------------------------------------------------------------------------------
SaveNodeCanonically(QXmlStreamWriter & stream,const QDomNode & domNode)67 void SaveNodeCanonically(QXmlStreamWriter &stream, const QDomNode &domNode)
68 {
69     if (stream.hasError())
70     {
71        return;
72     }
73 
74     if (domNode.isElement())
75     {
76         const QDomElement domElement = domNode.toElement();
77         if (not domElement.isNull())
78         {
79             stream.writeStartElement(domElement.tagName());
80 
81             if (domElement.hasAttributes())
82             {
83                 QMap<QString, QString> attributes;
84                 const QDomNamedNodeMap attributeMap = domElement.attributes();
85                 for (int i = 0; i < attributeMap.count(); ++i)
86                 {
87                     const QDomNode attribute = attributeMap.item(i);
88                     attributes.insert(attribute.nodeName(), attribute.nodeValue());
89                 }
90 
91                 QMap<QString, QString>::const_iterator i = attributes.constBegin();
92                 while (i != attributes.constEnd())
93                 {
94                     stream.writeAttribute(i.key(), i.value());
95                     ++i;
96                 }
97             }
98 
99             if (domElement.hasChildNodes())
100             {
101                 QDomNode elementChild = domElement.firstChild();
102                 while (not elementChild.isNull())
103                 {
104                     SaveNodeCanonically(stream, elementChild);
105                     elementChild = elementChild.nextSibling();
106                 }
107             }
108 
109             stream.writeEndElement();
110         }
111     }
112     else if (domNode.isComment())
113     {
114         stream.writeComment(domNode.nodeValue());
115     }
116     else if (domNode.isText())
117     {
118         stream.writeCharacters(domNode.nodeValue());
119     }
120 }
121 
122 //---------------------------------------------------------------------------------------------------------------------
GetChildElements(const QDomNode & e)123 QList<QDomNode> GetChildElements(const QDomNode& e)
124 {
125     QDomNodeList children = e.childNodes();
126     QList<QDomNode> r;
127     r.reserve(children.size());
128     for (int k = 0; k < children.size(); ++k)
129     {
130         r << children.at(k);
131     }
132     return r;
133 }
134 
135 //---------------------------------------------------------------------------------------------------------------------
GetElementAttributes(const QDomNode & e)136 QList<QDomNode> GetElementAttributes(const QDomNode& e)
137 {
138     QDomNamedNodeMap attributes = e.attributes();
139     QList<QDomNode> r;
140     r.reserve(attributes.size());
141     for (int k = 0; k < attributes.size(); ++k)
142     {
143         r << attributes.item(k);
144     }
145     return r;
146 }
147 
148 //---------------------------------------------------------------------------------------------------------------------
LessThen(const QDomNode & element1,const QDomNode & element2)149 bool LessThen(const QDomNode &element1, const QDomNode &element2)
150 {
151     if (element1.nodeType() != element2.nodeType())
152     {
153         return element1.nodeType() < element2.nodeType();
154     }
155 
156     QString tag1 = element1.nodeName();
157     QString tag2 = element2.nodeName();
158 
159     //qDebug() << tag1 <<tag2;
160     if (tag1 != tag2)
161     {
162         return tag1 < tag2;
163     }
164 
165     // Compare attributes
166     QList<QDomNode> attributes1 = GetElementAttributes(element1);
167     QList<QDomNode> attributes2 = GetElementAttributes(element2);
168 
169     if(attributes1.size() != attributes2.size())
170     {
171         return attributes1.size() < attributes2.size();
172     }
173 
174     auto CompareDomNodeLists = [](QList<QDomNode> list1, QList<QDomNode> list2, bool *stop)
175     {
176         *stop = false;
177         std::sort(list1.begin(), list1.end(), LessThen);
178         std::sort(list2.begin(), list2.end(), LessThen);
179         //qDebug() << "comparing sorted lists";
180         for(int k = 0; k < list1.size(); ++k)
181         {
182             if (!LessThen(list1[k], list2[k]))
183             {
184                 if (LessThen(list2[k], list1[k]))
185                 {
186                     *stop = true;
187                     //qDebug() << "false!";
188                     return false;
189                 }
190             }
191             else
192             {
193                 *stop = true;
194                 //qDebug() << "true!";
195                 return true;
196             }
197         }
198         return false;
199     };
200 
201     bool stop = false;
202     bool result = CompareDomNodeLists(attributes1, attributes2, &stop);
203     if (stop)
204     {
205         return result;
206     }
207 
208     // Compare children
209     QList<QDomNode> elts1 = GetChildElements(element1);
210     QList<QDomNode> elts2 = GetChildElements(element2);
211 
212     QString value1, value2;
213 
214     if(elts1.size() != elts2.size())
215     {
216         return elts1.size() < elts2.size();
217     }
218 
219     if(elts1.isEmpty())
220     {
221         value1 = element1.nodeValue();
222         value2 = element2.nodeValue();
223 
224         //qDebug() <<value1 << value2 << (value1 < value2);
225         return value1 < value2;
226     }
227 
228     result = CompareDomNodeLists(elts1, elts2, &stop);
229     if (stop)
230     {
231         return result;
232     }
233     return false;
234 }
235 }  // namespace
236 
237 Q_LOGGING_CATEGORY(vXML, "v.xml")
238 
239 const QString VDomDocument::AttrId          = QStringLiteral("id");
240 const QString VDomDocument::AttrText        = QStringLiteral("text");
241 const QString VDomDocument::AttrBold        = QStringLiteral("bold");
242 const QString VDomDocument::AttrItalic      = QStringLiteral("italic");
243 const QString VDomDocument::AttrAlignment   = QStringLiteral("alignment");
244 const QString VDomDocument::AttrFSIncrement = QStringLiteral("sfIncrement");
245 
246 const QString VDomDocument::TagVersion = QStringLiteral("version");
247 const QString VDomDocument::TagUnit    = QStringLiteral("unit");
248 const QString VDomDocument::TagLine    = QStringLiteral("line");
249 
250 //---------------------------------------------------------------------------------------------------------------------
VDomDocument(QObject * parent)251 VDomDocument::VDomDocument(QObject *parent)
252     : QObject(parent),
253       QDomDocument(),
254       m_elementIdCache(),
255       m_watcher(new QFutureWatcher<QHash<quint32, QDomElement>>(this))
256 {
257     connect(m_watcher, &QFutureWatcher<QHash<quint32, QDomElement>>::finished, this, &VDomDocument::CacheRefreshed);
258 }
259 
260 //---------------------------------------------------------------------------------------------------------------------
~VDomDocument()261 VDomDocument::~VDomDocument()
262 {
263     m_watcher->cancel();
264 }
265 
266 //---------------------------------------------------------------------------------------------------------------------
elementById(quint32 id,const QString & tagName,bool updateCache)267 QDomElement VDomDocument::elementById(quint32 id, const QString &tagName, bool updateCache)
268 {
269     if (id == 0)
270     {
271         return QDomElement();
272     }
273 
274     if (m_elementIdCache.contains(id))
275     {
276         const QDomElement e = m_elementIdCache.value(id);
277         if (e.parentNode().nodeType() != QDomNode::BaseNode)
278         {
279             if (not tagName.isEmpty())
280             {
281                 if (e.tagName() == tagName)
282                 {
283                     return e;
284                 }
285             }
286             else
287             {
288                 return e;
289             }
290         }
291     }
292 
293     if (updateCache)
294     {   // Cached missed
295         RefreshElementIdCache();
296     }
297 
298     if (tagName.isEmpty())
299     {
300         // Because VDomDocument::find checks for unique id we must use temp cache
301         QHash<quint32, QDomElement> tmpCache;
302         if (VDomDocument::find(tmpCache, this->documentElement(), id))
303         {
304             return tmpCache.value(id);
305         }
306     }
307     else
308     {
309         const QDomNodeList list = elementsByTagName(tagName);
310         for (int i=0; i < list.size(); ++i)
311         {
312             const QDomElement domElement = list.at(i).toElement();
313             if (not domElement.isNull() && domElement.hasAttribute(AttrId))
314             {
315                 const quint32 elementId = GetParametrUInt(domElement, AttrId, NULL_ID_STR);
316 
317                 if (elementId == id)
318                 {
319                     return domElement;
320                 }
321             }
322         }
323     }
324 
325     return QDomElement();
326 }
327 
328 //---------------------------------------------------------------------------------------------------------------------
329 /**
330  * @brief Find element by id.
331  * @param cache cache with element ids
332  * @param node node
333  * @param id id value
334  * @return true if found
335  */
find(QHash<quint32,QDomElement> & cache,const QDomElement & node,quint32 id)336 bool VDomDocument::find(QHash<quint32, QDomElement> &cache, const QDomElement &node, quint32 id)
337 {
338     if (node.hasAttribute(AttrId))
339     {
340         const quint32 elementId = GetParametrUInt(node, AttrId, NULL_ID_STR);
341 
342         if (cache.contains(elementId))
343         {
344             qWarning() << tr("Not unique id (%1)").arg(elementId);
345         }
346 
347         cache.insert(elementId, node);
348         if (elementId == id)
349         {
350             return true;
351         }
352     }
353 
354     for (qint32 i=0; i<node.childNodes().length(); ++i)
355     {
356         const QDomNode n = node.childNodes().at(i);
357         if (n.isElement())
358         {
359             if (VDomDocument::find(cache, n.toElement(), id))
360             {
361                 return true;
362             }
363         }
364     }
365     return false;
366 }
367 
368 
369 //---------------------------------------------------------------------------------------------------------------------
RefreshCache(const QDomElement & root) const370 QHash<quint32, QDomElement> VDomDocument::RefreshCache(const QDomElement &root) const
371 {
372     QHash<quint32, QDomElement> cache;
373     VDomDocument::find(cache, root, NULL_ID);
374     return cache;
375 }
376 
377 //---------------------------------------------------------------------------------------------------------------------
SaveCanonicalXML(QIODevice * file,int indent,QString & error) const378 bool VDomDocument::SaveCanonicalXML(QIODevice *file, int indent, QString &error) const
379 {
380     SCASSERT(file != nullptr)
381 
382     QXmlStreamWriter stream(file);
383     stream.setAutoFormatting(true);
384     stream.setAutoFormattingIndent(indent);
385     stream.writeStartDocument();
386 
387     QDomNode root = documentElement();
388     while (not root.isNull())
389     {
390         SaveNodeCanonically(stream, root);
391         if (stream.hasError())
392         {
393             break;
394         }
395         root = root.nextSibling();
396     }
397 
398     stream.writeEndDocument();
399 
400     if (stream.hasError())
401     {
402         error = tr("Fail to write Canonical XML.");
403         return false;
404     }
405     return true;
406 }
407 
408 //---------------------------------------------------------------------------------------------------------------------
409 /**
410  * @brief Returns the long long value of the given attribute. RENAME: GetParameterLongLong?
411  * @param domElement tag in xml tree
412  * @param name attribute name
413  * @return long long value
414  */
GetParametrUInt(const QDomElement & domElement,const QString & name,const QString & defValue)415 quint32 VDomDocument::GetParametrUInt(const QDomElement &domElement, const QString &name, const QString &defValue)
416 {
417     Q_ASSERT_X(not name.isEmpty(), Q_FUNC_INFO, "name of parametr is empty");
418     Q_ASSERT_X(not domElement.isNull(), Q_FUNC_INFO, "domElement is null"); //-V591
419 
420     bool ok = false;
421     QString parametr;
422     quint32 id = 0;
423 
424     try
425     {
426         parametr = GetParametrString(domElement, name, defValue);
427         id = parametr.toUInt(&ok);
428         if (ok == false)
429         {
430             throw VExceptionConversionError(QObject::tr("Can't convert toUInt parameter"), name);
431         }
432     }
433     catch (const VExceptionEmptyParameter &e)
434     {
435         VExceptionConversionError excep(QObject::tr("Can't convert toUInt parameter"), name);
436         excep.AddMoreInformation(e.ErrorMessage());
437         throw excep;
438     }
439 
440     return id;
441 }
442 
443 //---------------------------------------------------------------------------------------------------------------------
GetParametrInt(const QDomElement & domElement,const QString & name,const QString & defValue)444 int VDomDocument::GetParametrInt(const QDomElement &domElement, const QString &name, const QString &defValue)
445 {
446     Q_ASSERT_X(not name.isEmpty(), Q_FUNC_INFO, "name of parametr is empty");
447     Q_ASSERT_X(not domElement.isNull(), Q_FUNC_INFO, "domElement is null"); //-V591
448 
449     bool ok = false;
450     QString parametr;
451     int value = 0;
452 
453     try
454     {
455         parametr = GetParametrString(domElement, name, defValue);
456         value = parametr.toInt(&ok);
457         if (ok == false)
458         {
459             throw VExceptionConversionError(QObject::tr("Can't convert toInt parameter"), name);
460         }
461     }
462     catch (const VExceptionEmptyParameter &e)
463     {
464         VExceptionConversionError excep(QObject::tr("Can't convert toInt parameter"), name);
465         excep.AddMoreInformation(e.ErrorMessage());
466         throw excep;
467     }
468 
469     return value;
470 }
471 
472 //---------------------------------------------------------------------------------------------------------------------
GetParametrBool(const QDomElement & domElement,const QString & name,const QString & defValue)473 bool VDomDocument::GetParametrBool(const QDomElement &domElement, const QString &name, const QString &defValue)
474 {
475     Q_ASSERT_X(not name.isEmpty(), Q_FUNC_INFO, "name of parametr is empty");
476     Q_ASSERT_X(not domElement.isNull(), Q_FUNC_INFO, "domElement is null");
477 
478     QString parametr;
479     bool val = true;
480 
481     const QString message = QObject::tr("Can't convert toBool parameter");
482     try
483     {
484         parametr = GetParametrString(domElement, name, defValue);
485 
486         const QStringList bools {trueStr, falseStr, QChar('1'), QChar('0')};
487         switch (bools.indexOf(parametr))
488         {
489             case 0: // true
490             case 2: // 1
491                 val = true;
492                 break;
493             case 1: // false
494             case 3: // 0
495                 val = false;
496                 break;
497             default:// others
498                 throw VExceptionConversionError(message, name);
499         }
500     }
501     catch (const VExceptionEmptyParameter &e)
502     {
503         VExceptionConversionError excep(message, name);
504         excep.AddMoreInformation(e.ErrorMessage());
505         throw excep;
506     }
507 
508     return val;
509 }
510 
511 //---------------------------------------------------------------------------------------------------------------------
GetParametrUsage(const QDomElement & domElement,const QString & name)512 NodeUsage VDomDocument::GetParametrUsage(const QDomElement &domElement, const QString &name)
513 {
514     const bool value = GetParametrBool(domElement, name, trueStr);
515     if (value)
516     {
517         return NodeUsage::InUse;
518     }
519     else
520     {
521         return NodeUsage::NotInUse;
522     }
523 }
524 
525 //---------------------------------------------------------------------------------------------------------------------
SetParametrUsage(QDomElement & domElement,const QString & name,const NodeUsage & value)526 void VDomDocument::SetParametrUsage(QDomElement &domElement, const QString &name, const NodeUsage &value)
527 {
528     if (value == NodeUsage::InUse)
529     {
530         domElement.setAttribute(name, trueStr);
531     }
532     else
533     {
534         domElement.setAttribute(name, falseStr);
535     }
536 }
537 
538 //---------------------------------------------------------------------------------------------------------------------
539 /**
540  * @brief Returns the string value of the given attribute. RENAME: see above
541  *
542  * if attribute empty return default value. If default value empty too throw exception.
543  * @return attribute value
544  * @throw VExceptionEmptyParameter when attribute is empty
545  */
GetParametrString(const QDomElement & domElement,const QString & name,const QString & defValue)546 QString VDomDocument::GetParametrString(const QDomElement &domElement, const QString &name,
547                                         const QString &defValue)
548 {
549     Q_ASSERT_X(not name.isEmpty(), Q_FUNC_INFO, "name of parametr is empty");
550     Q_ASSERT_X(not domElement.isNull(), Q_FUNC_INFO, "domElement is null");
551     const QString parameter = domElement.attribute(name, defValue);
552     if (parameter.isEmpty())
553     {
554         if (defValue.isEmpty())
555         {
556             throw VExceptionEmptyParameter(QObject::tr("Got empty parameter"), name, domElement);
557         }
558         else
559         {
560             return defValue;
561         }
562     }
563     return parameter;
564 }
565 
566 //---------------------------------------------------------------------------------------------------------------------
GetParametrEmptyString(const QDomElement & domElement,const QString & name)567 QString VDomDocument::GetParametrEmptyString(const QDomElement &domElement, const QString &name)
568 {
569     Q_ASSERT_X(not name.isEmpty(), Q_FUNC_INFO, "name of parametr is empty");
570     Q_ASSERT_X(not domElement.isNull(), Q_FUNC_INFO, "domElement is null");
571     return domElement.attribute(name);
572 }
573 
574 //---------------------------------------------------------------------------------------------------------------------
575 /**
576  * @brief Returns the double value of the given attribute.
577  * @param domElement tag in xml tree
578  * @param name attribute name
579  * @return double value
580  */
GetParametrDouble(const QDomElement & domElement,const QString & name,const QString & defValue)581 qreal VDomDocument::GetParametrDouble(const QDomElement &domElement, const QString &name, const QString &defValue)
582 {
583     Q_ASSERT_X(not name.isEmpty(), Q_FUNC_INFO, "name of parametr is empty");
584     Q_ASSERT_X(not domElement.isNull(), Q_FUNC_INFO, "domElement is null");
585 
586     bool ok = false;
587     qreal param = 0;
588 
589     try
590     {
591         QString parametr = GetParametrString(domElement, name, defValue);
592         param = parametr.replace(QChar(','), QChar('.')).toDouble(&ok);
593         if (ok == false)
594         {
595             throw VExceptionConversionError(QObject::tr("Can't convert toDouble parameter"), name);
596         }
597     }
598     catch (const VExceptionEmptyParameter &e)
599     {
600         VExceptionConversionError excep(QObject::tr("Can't convert toDouble parameter"), name);
601         excep.AddMoreInformation(e.ErrorMessage());
602         throw excep;
603     }
604     return param;
605 }
606 
607 //---------------------------------------------------------------------------------------------------------------------
608 /**
609  * @brief GetParametrId return value id attribute.
610  * @param domElement tag in xml tree.
611  * @return id value.
612  */
GetParametrId(const QDomElement & domElement)613 quint32 VDomDocument::GetParametrId(const QDomElement &domElement)
614 {
615     Q_ASSERT_X(not domElement.isNull(), Q_FUNC_INFO, "domElement is null");
616 
617     quint32 id = NULL_ID;
618 
619     const QString message = QObject::tr("Got wrong parameter id. Need only id > 0.");
620     try
621     {
622         id = GetParametrUInt(domElement, VDomDocument::AttrId, NULL_ID_STR);
623         if (id == NULL_ID)
624         {
625             throw VExceptionWrongId(message, domElement);
626         }
627     }
628     catch (const VExceptionConversionError &e)
629     {
630         VExceptionWrongId excep(message, domElement);
631         excep.AddMoreInformation(e.ErrorMessage());
632         throw excep;
633     }
634     return id;
635 }
636 
637 //---------------------------------------------------------------------------------------------------------------------
UniqueTagText(const QString & tagName,const QString & defVal) const638 QString VDomDocument::UniqueTagText(const QString &tagName, const QString &defVal) const
639 {
640     const QDomNodeList nodeList = this->elementsByTagName(tagName);
641     if (nodeList.isEmpty())
642     {
643         return defVal;
644     }
645     else
646     {
647         const QDomNode domNode = nodeList.at(0);
648         if (domNode.isNull() == false && domNode.isElement())
649         {
650             const QDomElement domElement = domNode.toElement();
651             if (domElement.isNull() == false)
652             {
653                 const QString text = domElement.text();
654                 if (text.isEmpty())
655                 {
656                     return defVal;
657                 }
658                 else
659                 {
660                     return text;
661                 }
662             }
663         }
664     }
665     return defVal;
666 }
667 
668 //---------------------------------------------------------------------------------------------------------------------
669 /**
670  * @brief TestUniqueId test exist unique id in pattern file. Each id must be unique.
671  */
TestUniqueId() const672 void VDomDocument::TestUniqueId() const
673 {
674     QVector<quint32> vector;
675     CollectId(documentElement(), vector);
676 }
677 
678 //---------------------------------------------------------------------------------------------------------------------
CollectId(const QDomElement & node,QVector<quint32> & vector) const679 void VDomDocument::CollectId(const QDomElement &node, QVector<quint32> &vector) const
680 {
681     if (node.hasAttribute(VDomDocument::AttrId))
682     {
683         const quint32 id = GetParametrId(node);
684         if (vector.contains(id))
685         {
686             throw VExceptionWrongId(tr("This id (%1) is not unique.").arg(id), node);
687         }
688         vector.append(id);
689     }
690 
691     for (qint32 i=0; i<node.childNodes().length(); ++i)
692     {
693         const QDomNode n = node.childNodes().at(i);
694         if (n.isElement())
695         {
696             CollectId(n.toElement(), vector);
697         }
698     }
699 }
700 
701 //---------------------------------------------------------------------------------------------------------------------
RefreshElementIdCache()702 void VDomDocument::RefreshElementIdCache()
703 {
704     if (m_watcher->isFinished())
705     {
706         m_watcher->setFuture(QtConcurrent::run(this, &VDomDocument::RefreshCache, documentElement()));
707     }
708 }
709 
710 //---------------------------------------------------------------------------------------------------------------------
Compare(const QDomElement & element1,const QDomElement & element2)711 bool VDomDocument::Compare(const QDomElement &element1, const QDomElement &element2)
712 {
713     QFuture<bool> lessThen2 = QtConcurrent::run(LessThen, element2, element1);
714     return !LessThen(element1, element2) && !lessThen2.result();
715 }
716 
717 //---------------------------------------------------------------------------------------------------------------------
CacheRefreshed()718 void VDomDocument::CacheRefreshed()
719 {
720     m_elementIdCache = m_watcher->future().result();
721 }
722 
723 //---------------------------------------------------------------------------------------------------------------------
setXMLContent(const QString & fileName)724 void VDomDocument::setXMLContent(const QString &fileName)
725 {
726     QFile file(fileName);
727     // cppcheck-suppress ConfigurationNotChecked
728     if (file.open(QIODevice::ReadOnly) == false)
729     {
730         const QString errorMsg(tr("Can't open file %1:\n%2.").arg(fileName, file.errorString()));
731         throw VException(errorMsg);
732     }
733 
734     QString errorMsg;
735     int errorLine = -1;
736     int errorColumn = -1;
737     if (QDomDocument::setContent(&file, &errorMsg, &errorLine, &errorColumn) == false)
738     {
739         file.close();
740         VException e(errorMsg);
741         e.AddMoreInformation(tr("Parsing error file %3 in line %1 column %2").arg(errorLine).arg(errorColumn)
742                              .arg(fileName));
743         throw e;
744     }
745 
746     RefreshElementIdCache();
747 }
748 
749 //---------------------------------------------------------------------------------------------------------------------
UnitsHelpString()750 QString VDomDocument::UnitsHelpString()
751 {
752     QString r;
753     for (auto i = static_cast<int>(Unit::Mm), last = static_cast<int>(Unit::LAST_UNIT_DO_NOT_USE); i < last;++i)
754     {
755         r += UnitsToStr(static_cast<Unit>(i));
756         if (i < last - 1)
757         {
758             r += ", ";
759         }
760     }
761     return r;
762 }
763 
764 //---------------------------------------------------------------------------------------------------------------------
CreateElementWithText(const QString & tagName,const QString & text)765 QDomElement VDomDocument::CreateElementWithText(const QString &tagName, const QString &text)
766 {
767     QDomElement tag = createElement(tagName);
768     tag.appendChild(createTextNode(text));
769     return tag;
770 }
771 
772 //---------------------------------------------------------------------------------------------------------------------
SaveDocument(const QString & fileName,QString & error)773 bool VDomDocument::SaveDocument(const QString &fileName, QString &error)
774 {
775     if (fileName.isEmpty())
776     {
777         qDebug()<<"Got empty file name.";
778         return false;
779     }
780     bool success = false;
781     QSaveFile file(fileName);
782     // cppcheck-suppress ConfigurationNotChecked
783     if (file.open(QIODevice::WriteOnly))
784     {
785         // See issue #666. QDomDocument produces random attribute order.
786         const int indent = 4;
787         if (not SaveCanonicalXML(&file, indent, error))
788         {
789             return false;
790         }
791         // Left these strings in case we will need them for testing purposes
792         // QTextStream out(&file);
793         // out.setCodec("UTF-8");
794         // save(out, indent);
795 
796         success = file.commit();
797     }
798 
799     if (not success)
800     {
801         error = file.errorString();
802     }
803 
804     return success;
805 }
806 
807 //---------------------------------------------------------------------------------------------------------------------
808 // cppcheck-suppress unusedFunction
Major() const809 QString VDomDocument::Major() const
810 {
811     QString version = UniqueTagText(TagVersion, "0.0.0");
812     QStringList v = version.split(QChar('.'));
813     return v.at(0);
814 }
815 
816 //---------------------------------------------------------------------------------------------------------------------
817 // cppcheck-suppress unusedFunction
Minor() const818 QString VDomDocument::Minor() const
819 {
820     QString version = UniqueTagText(TagVersion, "0.0.0");
821     QStringList v = version.split(QChar('.'));
822     return v.at(1);
823 }
824 
825 //---------------------------------------------------------------------------------------------------------------------
826 // cppcheck-suppress unusedFunction
Patch() const827 QString VDomDocument::Patch() const
828 {
829     QString version = UniqueTagText(TagVersion, "0.0.0");
830     QStringList v = version.split(QChar('.'));
831     return v.at(2);
832 }
833 
834 //---------------------------------------------------------------------------------------------------------------------
GetFormatVersionStr() const835 QString VDomDocument::GetFormatVersionStr() const
836 {
837     const QDomNodeList nodeList = this->elementsByTagName(TagVersion);
838     if (nodeList.isEmpty())
839     {
840         const QString errorMsg(tr("Couldn't get version information."));
841         throw VException(errorMsg);
842     }
843 
844     if (nodeList.count() > 1)
845     {
846         const QString errorMsg(tr("Too many tags <%1> in file.").arg(TagVersion));
847         throw VException(errorMsg);
848     }
849 
850     const QDomNode domNode = nodeList.at(0);
851     if (domNode.isNull() == false && domNode.isElement())
852     {
853         const QDomElement domElement = domNode.toElement();
854         if (domElement.isNull() == false)
855         {
856             return domElement.text();
857         }
858     }
859     return QString(QStringLiteral("0.0.0"));
860 }
861 
862 //---------------------------------------------------------------------------------------------------------------------
GetFormatVersion(const QString & version)863 int VDomDocument::GetFormatVersion(const QString &version)
864 {
865     ValidateVersion(version);
866 
867     const QStringList ver = version.split(QChar('.'));
868 
869     bool ok = false;
870     const int major = ver.at(0).toInt(&ok);
871     if (not ok)
872     {
873         return 0x0;
874     }
875 
876     ok = false;
877     const int minor = ver.at(1).toInt(&ok);
878     if (not ok)
879     {
880         return 0x0;
881     }
882 
883     ok = false;
884     const int patch = ver.at(2).toInt(&ok);
885     if (not ok)
886     {
887         return 0x0;
888     }
889 
890     return (major<<16)|(minor<<8)|(patch);
891 }
892 
893 //---------------------------------------------------------------------------------------------------------------------
setTagText(const QString & tag,const QString & text)894 bool VDomDocument::setTagText(const QString &tag, const QString &text)
895 {
896     const QDomNodeList nodeList = this->elementsByTagName(tag);
897     if (nodeList.isEmpty())
898     {
899         qDebug()<<"Can't save tag "<<tag<<Q_FUNC_INFO;
900     }
901     else
902     {
903         const QDomNode domNode = nodeList.at(0);
904         if (domNode.isNull() == false && domNode.isElement())
905         {
906             const QDomElement domElement = domNode.toElement();
907             return setTagText(domElement, text);
908         }
909     }
910     return false;
911 }
912 
913 //---------------------------------------------------------------------------------------------------------------------
setTagText(const QDomElement & domElement,const QString & text)914 bool VDomDocument::setTagText(const QDomElement &domElement, const QString &text)
915 {
916     if (domElement.isNull() == false)
917     {
918         QDomElement parent = domElement.parentNode().toElement();
919         QDomElement newTag = createElement(domElement.tagName());
920 
921         const QDomText newTagText = createTextNode(text);
922         newTag.appendChild(newTagText);
923 
924         parent.replaceChild(newTag, domElement);
925         return true;
926     }
927     return false;
928 }
929 
930 //---------------------------------------------------------------------------------------------------------------------
931 /**
932  * @brief RemoveAllChildren remove all children from file.
933  * @param domElement tag in xml tree.
934  */
RemoveAllChildren(QDomElement & domElement)935 void VDomDocument::RemoveAllChildren(QDomElement &domElement)
936 {
937     if ( domElement.hasChildNodes() )
938     {
939         while ( domElement.childNodes().length() >= 1 )
940         {
941             domElement.removeChild( domElement.firstChild() );
942         }
943     }
944 }
945 
946 //---------------------------------------------------------------------------------------------------------------------
ParentNodeById(const quint32 & nodeId)947 QDomNode VDomDocument::ParentNodeById(const quint32 &nodeId)
948 {
949     QDomElement domElement = NodeById(nodeId);
950     return domElement.parentNode();
951 }
952 
953 //---------------------------------------------------------------------------------------------------------------------
CloneNodeById(const quint32 & nodeId)954 QDomElement VDomDocument::CloneNodeById(const quint32 &nodeId)
955 {
956     QDomElement domElement = NodeById(nodeId);
957     return domElement.cloneNode().toElement();
958 }
959 
960 //---------------------------------------------------------------------------------------------------------------------
NodeById(const quint32 & nodeId,const QString & tagName)961 QDomElement VDomDocument::NodeById(const quint32 &nodeId, const QString &tagName)
962 {
963     QDomElement domElement = elementById(nodeId, tagName);
964     if (domElement.isNull() || domElement.isElement() == false)
965     {
966         throw VExceptionBadId(tr("Couldn't get node"), nodeId);
967     }
968     return domElement;
969 }
970 
971 //---------------------------------------------------------------------------------------------------------------------
SafeCopy(const QString & source,const QString & destination,QString & error)972 bool VDomDocument::SafeCopy(const QString &source, const QString &destination, QString &error)
973 {
974     bool result = false;
975 
976 #ifdef Q_OS_WIN32
977     qt_ntfs_permission_lookup++; // turn checking on
978 #endif /*Q_OS_WIN32*/
979 
980     QTemporaryFile destFile(destination + QLatin1String(".XXXXXX"));
981     destFile.setAutoRemove(false);// Will be renamed to be destination file
982     // cppcheck-suppress ConfigurationNotChecked
983     if (not destFile.open())
984     {
985         error = destFile.errorString();
986     }
987     else
988     {
989         QFile sourceFile(source);
990         // cppcheck-suppress ConfigurationNotChecked
991         if (sourceFile.open(QIODevice::ReadOnly))
992         {
993             result = true;
994             char block[4096];
995             qint64 bytes;
996             while ((bytes = sourceFile.read(block, sizeof(block))) > 0)
997             {
998                 if (bytes != destFile.write(block, bytes))
999                 {
1000                     error = destFile.errorString();
1001                     result = false;
1002                     break;
1003                 }
1004             }
1005 
1006             if (bytes == -1)
1007             {
1008                 error = sourceFile.errorString();
1009                 result = false;
1010             }
1011 
1012             if (result)
1013             {
1014                 QFile::remove(destination);
1015                 if (not destFile.rename(destination))
1016                 {
1017                     error = destFile.errorString();
1018                     result = false;
1019                 }
1020                 else
1021                 {
1022                     result = true;
1023                 }
1024             }
1025         }
1026         else
1027         {
1028             error = sourceFile.errorString();
1029         }
1030     }
1031 
1032 #ifdef Q_OS_WIN32
1033     qt_ntfs_permission_lookup--; // turn off check permission again
1034 #endif /*Q_OS_WIN32*/
1035 
1036     return result;
1037 }
1038 
1039 //---------------------------------------------------------------------------------------------------------------------
GetLabelTemplate(const QDomElement & element) const1040 QVector<VLabelTemplateLine> VDomDocument::GetLabelTemplate(const QDomElement &element) const
1041 {
1042     // We use implicit conversion. That's why check if values are still the same as excpected.
1043     Q_STATIC_ASSERT(Qt::AlignLeft == 1);
1044     Q_STATIC_ASSERT(Qt::AlignRight == 2);
1045     Q_STATIC_ASSERT(Qt::AlignHCenter == 4);
1046 
1047     QVector<VLabelTemplateLine> lines;
1048 
1049     if (not element.isNull())
1050     {
1051         QDomElement tagLine = element.firstChildElement();
1052         while (tagLine.isNull() == false)
1053         {
1054             if (tagLine.tagName() == TagLine)
1055             {
1056                 VLabelTemplateLine line;
1057                 line.line = GetParametrString(tagLine, AttrText, tr("<empty>"));
1058                 line.bold = GetParametrBool(tagLine, AttrBold, falseStr);
1059                 line.italic = GetParametrBool(tagLine, AttrItalic, falseStr);
1060                 line.alignment = static_cast<int>(GetParametrUInt(tagLine, AttrAlignment, QChar('0')));
1061                 line.fontSizeIncrement = static_cast<int>(GetParametrUInt(tagLine, AttrFSIncrement, QChar('0')));
1062                 lines.append(line);
1063             }
1064             tagLine = tagLine.nextSiblingElement(TagLine);
1065         }
1066     }
1067 
1068     return lines;
1069 }
1070 
1071 //---------------------------------------------------------------------------------------------------------------------
SetLabelTemplate(QDomElement & element,const QVector<VLabelTemplateLine> & lines)1072 void VDomDocument::SetLabelTemplate(QDomElement &element, const QVector<VLabelTemplateLine> &lines)
1073 {
1074     if (not element.isNull())
1075     {
1076         for (auto &line : lines)
1077         {
1078             QDomElement tagLine = createElement(TagLine);
1079 
1080             SetAttribute(tagLine, AttrText, line.line);
1081             SetAttribute(tagLine, AttrBold, line.bold);
1082             SetAttribute(tagLine, AttrItalic, line.italic);
1083             SetAttribute(tagLine, AttrAlignment, line.alignment);
1084             SetAttribute(tagLine, AttrFSIncrement, line.fontSizeIncrement);
1085 
1086             element.appendChild(tagLine);
1087         }
1088     }
1089 }
1090 
1091 //---------------------------------------------------------------------------------------------------------------------
ValidateVersion(const QString & version)1092 void VDomDocument::ValidateVersion(const QString &version)
1093 {
1094     const QRegularExpression rx(QStringLiteral("^([0-9]|[1-9][0-9]|[1-2][0-5][0-5]).([0-9]|[1-9][0-9]|[1-2][0-5][0-5])"
1095                                                ".([0-9]|[1-9][0-9]|[1-2][0-5][0-5])$"));
1096 
1097     if (rx.match(version).hasMatch() == false)
1098     {
1099         const QString errorMsg(tr("Version \"%1\" invalid.").arg(version));
1100         throw VException(errorMsg);
1101     }
1102 
1103     if (version == QLatin1String("0.0.0"))
1104     {
1105         const QString errorMsg(tr("Version \"0.0.0\" invalid."));
1106         throw VException(errorMsg);
1107     }
1108 }
1109