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