MyGUI 3.4.3
MyGUI_XmlDocument.cpp
Go to the documentation of this file.
1/*
2 * This source file is part of MyGUI. For the latest info, see http://mygui.info/
3 * Distributed under the MIT License
4 * (See accompanying file COPYING.MIT or copy at http://opensource.org/licenses/MIT)
5 */
6
7#include "MyGUI_Precompiled.h"
8#include "MyGUI_XmlDocument.h"
9#include "MyGUI_DataManager.h"
10
11namespace MyGUI::xml
12{
13
14 namespace utility
15 {
16 static std::string convert_from_xml(std::string_view _string, bool& _ok)
17 {
18 std::string ret;
19 _ok = true;
20
21 size_t pos = _string.find('&');
22 if (pos == std::string::npos)
23 return std::string{_string};
24
25 ret.reserve(_string.size());
26 size_t old = 0;
27 while (pos != std::string::npos)
28 {
29 ret += _string.substr(old, pos - old);
30
31 size_t end = _string.find(';', pos + 1);
32 if (end == std::string::npos)
33 {
34 _ok = false;
35 return ret;
36 }
37
38 std::string_view tag = _string.substr(pos, end - pos + 1);
39 if (tag == "&")
40 ret += '&';
41 else if (tag == "<")
42 ret += '<';
43 else if (tag == "&gt;")
44 ret += '>';
45 else if (tag == "&apos;")
46 ret += '\'';
47 else if (tag == "&quot;")
48 ret += '\"';
49 else
50 {
51 _ok = false;
52 return ret;
53 }
54
55 old = end + 1;
56 pos = _string.find('&', old);
57 }
58 ret += _string.substr(old, std::string::npos);
59
60 return ret;
61 }
62
63 static std::string convert_to_xml(std::string_view _string)
64 {
65 std::string ret;
66
67 size_t pos = _string.find_first_of("&<>'\"");
68 if (pos == std::string::npos)
69 return std::string{_string};
70
71 ret.reserve(_string.size() * 2);
72 size_t old = 0;
73 while (pos != std::string::npos)
74 {
75 ret += _string.substr(old, pos - old);
76
77 if (_string[pos] == '&')
78 ret += "&amp;";
79 else if (_string[pos] == '<')
80 ret += "&lt;";
81 else if (_string[pos] == '>')
82 ret += "&gt;";
83 else if (_string[pos] == '\'')
84 ret += "&apos;";
85 else if (_string[pos] == '\"')
86 ret += "&quot;";
87
88 old = pos + 1;
89 pos = _string.find_first_of("&<>'\"", old);
90 }
91 ret += _string.substr(old, std::string::npos);
92
93 return ret;
94 }
95
96 }
97
98 //----------------------------------------------------------------------//
99 // class ElementEnumerator
100 //----------------------------------------------------------------------//
101 ElementEnumerator::ElementEnumerator(VectorElement::iterator _begin, VectorElement::iterator _end) :
102 m_current(_begin),
103 m_end(_end)
104 {
105 }
106
108 {
109 if (m_current == m_end)
110 return false;
111 if (m_first)
112 {
113 m_first = false;
114 return true;
115 }
116 ++m_current;
117 return m_current != m_end;
118 }
119
120 bool ElementEnumerator::next(std::string_view _name)
121 {
122 while (next())
123 {
124 if ((*m_current)->getName() == _name)
125 return true;
126 }
127 return false;
128 }
129
131 {
132 assert(m_current != m_end);
133 return m_current->get();
134 }
135
137 {
138 assert(m_current != m_end);
139 return m_current->get();
140 }
141
142 //----------------------------------------------------------------------//
143 // class Element
144 //----------------------------------------------------------------------//
145 Element::Element(std::string_view _name, ElementPtr _parent, ElementType _type, std::string_view _content) :
146 mName(_name),
147 mContent(_content),
148 mParent(_parent),
149 mType(_type)
150 {
151 }
152
153 void Element::save(std::ostream& _stream, size_t _level)
154 {
155 // сначала табуляции намутим
156 for (size_t tab = 0; tab < _level; ++tab)
157 _stream << " ";
158
159 // теперь заголовок тега
160 if (mType == ElementType::Declaration)
161 _stream << "<?";
162 else if (mType == ElementType::Comment)
163 _stream << "<!--";
164 else
165 _stream << "<";
166
167 _stream << mName;
168
169 for (auto& attribute : mAttributes)
170 {
171 _stream << " " << attribute.first << "=\"" << utility::convert_to_xml(attribute.second) << "\"";
172 }
173
174 bool empty = mChildren.empty();
175 // если детей нет то закрываем
176 if (empty && mContent.empty())
177 {
178 if (mType == ElementType::Declaration)
179 _stream << "?>\n";
180 else if (mType == ElementType::Comment)
181 _stream << "-->\n";
182 else
183 _stream << "/>\n";
184 }
185 else
186 {
187 _stream << ">";
188 if (!empty)
189 _stream << "\n";
190 // если есть тело то сначало оно
191 if (!mContent.empty())
192 {
193 if (!empty)
194 {
195 for (size_t tab = 0; tab <= _level; ++tab)
196 _stream << " ";
197 }
198 _stream << utility::convert_to_xml(mContent);
199
200 if (!empty)
201 _stream << "\n";
202 }
203 // save child items
204 for (const auto& child : mChildren)
205 {
206 child->save(_stream, _level + 1);
207 }
208
209 if (!empty)
210 {
211 for (size_t tab = 0; tab < _level; ++tab)
212 _stream << " ";
213 }
214 _stream << "</" << mName << ">\n";
215 }
216 }
217
218 ElementPtr Element::createChild(std::string_view _name, std::string_view _content, ElementType _type)
219 {
220 return mChildren.emplace_back(std::make_unique<Element>(_name, this, _type, _content)).get();
221 }
222
224 {
225 for (auto iter = mChildren.begin(); iter != mChildren.end(); ++iter)
226 {
227 if (iter->get() == _child)
228 {
229 mChildren.erase(iter);
230 break;
231 }
232 }
233 }
234
236 {
237 mChildren.clear();
238 mContent.clear();
239 mAttributes.clear();
240 }
241
242 bool Element::findAttribute(std::string_view _name, std::string& _value)
243 {
244 for (const auto& attribute : mAttributes)
245 {
246 if (attribute.first == _name)
247 {
248 _value = attribute.second;
249 return true;
250 }
251 }
252 return false;
253 }
254
255 std::string_view Element::findAttribute(std::string_view _name)
256 {
257 for (const auto& attribute : mAttributes)
258 {
259 if (attribute.first == _name)
260 return attribute.second;
261 }
262 return {};
263 }
264
265 void Element::addAttribute(std::string_view _key, std::string_view _value)
266 {
267 mAttributes.emplace_back(_key, _value);
268 }
269
270 void Element::removeAttribute(std::string_view _key)
271 {
272 for (size_t index = 0; index < mAttributes.size(); ++index)
273 {
274 if (mAttributes[index].first == _key)
275 {
276 mAttributes.erase(mAttributes.begin() + index);
277 return;
278 }
279 }
280 }
281
282 std::unique_ptr<Element> Element::createCopy()
283 {
284 auto elem = std::make_unique<Element>(mName, nullptr, mType, mContent);
285 elem->mAttributes = mAttributes;
286
287 for (const auto& oldChild : mChildren)
288 {
289 auto child = oldChild->createCopy();
290 child->mParent = elem.get();
291 elem->mChildren.emplace_back(std::move(child));
292 }
293
294 return elem;
295 }
296
297 void Element::setAttribute(std::string_view _key, std::string_view _value)
298 {
299 for (auto& attribute : mAttributes)
300 {
301 if (attribute.first == _key)
302 {
303 attribute.second = _value;
304 return;
305 }
306 }
307 mAttributes.emplace_back(_key, _value);
308 }
309
310 void Element::addContent(std::string_view _content)
311 {
312 if (mContent.empty())
313 {
314 mContent = _content;
315 }
316 else
317 {
318 mContent += " ";
319 mContent += _content;
320 }
321 }
322
323 void Element::setContent(std::string_view _content)
324 {
325 mContent = _content;
326 }
327
328 const std::string& Element::getName() const
329 {
330 return mName;
331 }
332
333 const std::string& Element::getContent() const
334 {
335 return mContent;
336 }
337
339 {
340 return mAttributes;
341 }
342
344 {
345 return mParent;
346 }
347
349 {
350 return {mChildren.begin(), mChildren.end()};
351 }
352
354 {
355 return mType;
356 }
357
358#if MYGUI_COMPILER == MYGUI_COMPILER_MSVC && !defined(STLPORT)
359 inline void open_stream(std::ofstream& _stream, const std::wstring& _wide)
360 {
361 _stream.open(_wide.c_str());
362 }
363 inline void open_stream(std::ifstream& _stream, const std::wstring& _wide)
364 {
365 _stream.open(_wide.c_str());
366 }
367#else
368 inline void open_stream(std::ofstream& _stream, const std::wstring& _wide)
369 {
370 _stream.open(UString(_wide).asUTF8_c_str());
371 }
372 inline void open_stream(std::ifstream& _stream, const std::wstring& _wide)
373 {
374 _stream.open(UString(_wide).asUTF8_c_str());
375 }
376#endif
377
378 //----------------------------------------------------------------------//
379 // class Document
380 //----------------------------------------------------------------------//
381
382 // открывает обычным файлом, имя файла в utf8
383 bool Document::open(const std::string& _filename)
384 {
385 std::ifstream stream;
386 stream.open(_filename.c_str());
387
388 if (!stream.is_open())
389 {
390 mLastError = ErrorType::OpenFileFail;
391 setLastFileError(_filename);
392 return false;
393 }
394
395 bool result = open(stream);
396
397 stream.close();
398 return result;
399 }
400
401 // открывает обычным файлом, имя файла в utf16 или utf32
402 bool Document::open(const std::wstring& _filename)
403 {
404 std::ifstream stream;
405 open_stream(stream, _filename);
406
407 if (!stream.is_open())
408 {
409 mLastError = ErrorType::OpenFileFail;
410 setLastFileError(_filename);
411 return false;
412 }
413
414 bool result = open(stream);
415
416 stream.close();
417 return result;
418 }
419
420 bool Document::open(std::istream& _stream)
421 {
422 auto data = std::make_unique<DataStream>(&_stream);
423
424 bool result = open(data.get());
425
426 return result;
427 }
428
429 // сохраняет файл, имя файла в кодировке utf8
430 bool Document::save(const std::string& _filename)
431 {
432 std::ofstream stream;
433 stream.open(_filename.c_str());
434
435 if (!stream.is_open())
436 {
437 mLastError = ErrorType::CreateFileFail;
438 setLastFileError(_filename);
439 return false;
440 }
441
442 bool result = save(stream);
443
444 if (!result)
445 {
446 setLastFileError(_filename);
447 }
448
449 stream.close();
450 return result;
451 }
452
453 // сохраняет файл, имя файла в кодировке utf16 или utf32
454 bool Document::save(const std::wstring& _filename)
455 {
456 std::ofstream stream;
457 open_stream(stream, _filename);
458
459 if (!stream.is_open())
460 {
461 mLastError = ErrorType::CreateFileFail;
462 setLastFileError(_filename);
463 return false;
464 }
465
466 bool result = save(stream);
467
468 if (!result)
469 {
470 setLastFileError(_filename);
471 }
472
473 stream.close();
474 return result;
475 }
476
477 // открывает обычным потоком
479 {
480 clear();
481
482 // это текущая строка для разбора
483 std::string line;
484 // это строка из файла
485 std::string read;
486 // текущий узел для разбора
487 ElementPtr currentNode = nullptr;
488
489 while (!_stream->eof())
490 {
491 // берем новую строку
492 _stream->readline(read, '\n');
493 if (read.empty())
494 continue;
495 if (read[read.size() - 1] == '\r')
496 read.erase(read.size() - 1, 1);
497 if (read.empty())
498 continue;
499
500 mLine++;
501 mCol = 0; // потом проверить на многострочных тэгах
502
503 // текущая строка для разбора и то что еще прочитали
504 line += read;
505
506 if (!parseLine(line, currentNode))
507 {
508 return false;
509 }
510
511 } // while (!stream.eof())
512
513 if (currentNode)
514 {
515 mLastError = ErrorType::NotClosedElements;
516 return false;
517 }
518
519 return true;
520 }
521
522 bool Document::save(std::ostream& _stream)
523 {
524 if (!mDeclaration)
525 {
526 mLastError = ErrorType::NoXMLDeclaration;
527 return false;
528 }
529
530 // заголовок utf8
531 _stream << (char)0xEFu;
532 _stream << (char)0xBBu;
533 _stream << (char)0xBFu;
534
535 mDeclaration->save(_stream, 0);
536 if (mRoot)
537 mRoot->save(_stream, 0);
538
539 return true;
540 }
541
543 {
544 clearDeclaration();
545 clearRoot();
546 mLine = 0;
547 mCol = 0;
548 }
549
550 bool Document::parseTag(ElementPtr& _currentNode, std::string _content)
551 {
552 // убераем лишнее
553 MyGUI::utility::trim(_content);
554
555 if (_content.empty())
556 {
557 // создаем пустой тег
558 if (_currentNode)
559 {
560 _currentNode = _currentNode->createChild(std::string_view{});
561 }
562 else if (!mRoot)
563 {
564 mRoot = std::make_unique<Element>(std::string_view{}, nullptr);
565 _currentNode = mRoot.get();
566 }
567 return true;
568 }
569
570 char symbol = _content[0];
571 bool tagDeclaration = false;
572
573 // проверяем на коментарии
574 if (symbol == '!')
575 {
576 if (_currentNode != nullptr)
577 {
578 //_currentNode->createChild(std::string_view{}, _content, ElementType::Comment);
579 }
580 return true;
581 }
582 // проверяем на информационный тег
583 if (symbol == '?')
584 {
585 tagDeclaration = true;
586 _content.erase(0, 1); // удаляем первый символ
587 }
588
589 size_t start = 0;
590 // проверяем на закрытие тега
591 if (symbol == '/')
592 {
593 if (_currentNode == nullptr)
594 {
596 return false;
597 }
598 // обрезаем имя тэга
599 start = _content.find_first_not_of(" \t", 1);
600 if (start == std::string::npos)
601 {
602 // тег пустой
603 _content.clear();
604 }
605 else
606 {
607 size_t end = _content.find_last_not_of(" \t");
608 _content = _content.substr(start, end - start + 1);
609 }
610 // проверяем соответствие открывающего и закрывающего тегов
611 if (_currentNode->getName() != _content)
612 {
614 return false;
615 }
616 // а теперь снижаем текущий узел вниз
617 _currentNode = _currentNode->getParent();
618 }
619 else
620 {
621 // выделяем имя до первого пробела или закрывающего тега
622 std::string cut = _content;
623 start = _content.find_first_of(" \t/?", 1); // << превед
624 if (start != std::string::npos)
625 {
626 cut = _content.substr(0, start);
627 _content = _content.substr(start);
628 }
629 else
630 {
631 _content.clear();
632 }
633
634 if (_currentNode)
635 {
636 _currentNode = _currentNode->createChild(cut);
637 }
638 else
639 {
640 if (tagDeclaration)
641 {
642 // информационный тег
643 if (mDeclaration)
644 {
646 return false;
647 }
648 mDeclaration = std::make_unique<Element>(cut, nullptr, ElementType::Declaration);
649 _currentNode = mDeclaration.get();
650 }
651 else
652 {
653 // рутовый тег
654 if (mRoot)
655 {
657 return false;
658 }
659 mRoot = std::make_unique<Element>(cut, nullptr, ElementType::Normal);
660 _currentNode = mRoot.get();
661 }
662 }
663
664 // проверим на пустоту
665 start = _content.find_last_not_of(" \t");
666 if (start == std::string::npos)
667 return true;
668
669 // сразу отделим закрывающийся тэг
670 bool close = false;
671 if ((_content[start] == '/') || (_content[start] == '?'))
672 {
673 close = true;
674 // не будем резать строку, просто поставим пробел
675 _content[start] = ' ';
676 // проверим на пустоту
677 start = _content.find_last_not_of(" \t");
678 if (start == std::string::npos)
679 {
680 // возвращаем все назад и уходим
681 _currentNode = _currentNode->getParent();
682 return true;
683 }
684 }
685
686 // а вот здесь уже в цикле разбиваем на атрибуты
687 while (true)
688 {
689 // ищем равно
690 start = _content.find('=');
691 if (start == std::string::npos)
692 {
694 return false;
695 }
696 // ищем вторые ковычки
697 size_t end = _content.find_first_of("\"\'", start + 1);
698 if (end == std::string::npos)
699 {
701 return false;
702 }
703 end = _content.find_first_of("\"\'", end + 1);
704 if (end == std::string::npos)
705 {
707 return false;
708 }
709
710 std::string key = _content.substr(0, start);
711 std::string value = _content.substr(start + 1, end - start);
712
713 // проверка на валидность
714 if (!checkPair(key, value))
715 {
717 return false;
718 }
719
720 // добавляем пару в узел
721 _currentNode->addAttribute(key, value);
722
723 // следующий кусок
724 _content = _content.substr(end + 1);
725
726 // в строке не осталось символов
727 start = _content.find_first_not_of(" \t");
728 if (start == std::string::npos)
729 break;
730
731 mCol += start;
732 }
733
734 // был закрывающий тег для текущего тега
735 if (close)
736 {
737 // не проверяем имена, потому что это наш тэг
738 _currentNode = _currentNode->getParent();
739 }
740 }
741 return true;
742 }
743
744 bool Document::checkPair(std::string& _key, std::string& _value)
745 {
746 // в ключе не должно быть ковычек и пробелов
748 if (_key.empty())
749 return false;
750 size_t start = _key.find_first_of(" \t\"\'&");
751 if (start != std::string::npos)
752 return false;
753
754 // в значении, ковычки по бокам
755 MyGUI::utility::trim(_value);
756 if (_value.size() < 2)
757 return false;
758 if (((_value[0] != '"') || (_value[_value.length() - 1] != '"')) &&
759 ((_value[0] != '\'') || (_value[_value.length() - 1] != '\'')))
760 return false;
761 bool ok = true;
762 _value = utility::convert_from_xml(_value.substr(1, _value.length() - 2), ok);
763 return ok;
764 }
765
766 // ищет символ без учета ковычек
767 size_t Document::find(std::string_view _text, char _char, size_t _start)
768 {
769 // ковычки
770 bool kov = false;
771
772 // буфер для поиска
773 char buff[16] = "\"_\0";
774 buff[1] = _char;
775
776 size_t pos = _start;
777
778 while (true)
779 {
780 pos = _text.find_first_of(buff, pos);
781
782 // если уже конец, то досвидания
783 if (pos == std::string::npos)
784 {
785 break;
786 }
787 // нашли ковычку
788 if (_text[pos] == '"')
789 {
790 kov = !kov;
791 pos++;
792 }
793 // если мы в ковычках, то идем дальше
794 else if (kov)
795 {
796 pos++;
797 }
798 // мы не в ковычках
799 else
800 {
801 break;
802 }
803 }
804
805 return pos;
806 }
807
808 void Document::clearDeclaration()
809 {
810 mDeclaration.reset();
811 }
812
813 void Document::clearRoot()
814 {
815 mRoot.reset();
816 }
817
818 ElementPtr Document::createDeclaration(std::string_view _version, std::string_view _encoding)
819 {
820 clearDeclaration();
821 mDeclaration = std::make_unique<Element>("xml", nullptr, ElementType::Declaration);
822 mDeclaration->addAttribute("version", _version);
823 mDeclaration->addAttribute("encoding", _encoding);
824 return mDeclaration.get();
825 }
826
827 ElementPtr Document::createRoot(std::string_view _name)
828 {
829 clearRoot();
830 mRoot = std::make_unique<Element>(_name, nullptr, ElementType::Normal);
831 return mRoot.get();
832 }
833
834 bool Document::parseLine(std::string& _line, ElementPtr& _element)
835 {
836 // крутимся пока в строке есть теги
837 while (true)
838 {
839 // сначала ищем по угловым скобкам
840 size_t start = find(_line, '<');
841 if (start == std::string::npos)
842 break;
843 size_t end;
844
845 // пытаемся вырезать многострочный коментарий
846 if ((start + 3 < _line.size()) && (_line[start + 1] == '!') && (_line[start + 2] == '-') &&
847 (_line[start + 3] == '-'))
848 {
849 end = _line.find("-->", start + 4);
850 if (end == std::string::npos)
851 break;
852 end += 2;
853 }
854 else
855 {
856 end = find(_line, '>', start + 1);
857 if (end == std::string::npos)
858 break;
859 }
860 // проверяем на наличее тела
861 size_t body = _line.find_first_not_of(" \t<");
862 if (body < start)
863 {
864 std::string_view body_str = std::string_view{_line}.substr(0, start);
865 // текущий символ
866 mCol = 0;
867
868 if (_element != nullptr)
869 {
870 bool ok = true;
871 _element->setContent(utility::convert_from_xml(body_str, ok));
872 if (!ok)
873 {
874 mLastError = ErrorType::IncorrectContent;
875 return false;
876 }
877 }
878 }
879 // вырезаем наш тэг и парсим
880 if (!parseTag(_element, _line.substr(start + 1, end - start - 1)))
881 {
882 return false;
883 }
884 // и обрезаем текущую строку разбора
885 _line = _line.substr(end + 1);
886 }
887 return true;
888 }
889
890 std::string Document::getLastError() const
891 {
892 std::string_view error = mLastError.print();
893 if (error.empty())
894 return {};
896 "'",
897 error,
898 "' , file='",
899 mLastErrorFile,
900 "' , line=",
901 mLine,
902 " , col=",
903 mCol);
904 }
905
906 bool Document::open(const UString& _filename)
907 {
908 return open(_filename.asWStr());
909 }
910
911 bool Document::save(const UString& _filename)
912 {
913 return save(_filename.asWStr());
914 }
915
917 {
918 mLastError = ErrorType::MAX;
919 }
920
922 {
923 return mRoot.get();
924 }
925
926 void Document::setLastFileError(std::string_view _filename)
927 {
928 mLastErrorFile = _filename;
929 }
930
931 void Document::setLastFileError(const std::wstring& _filename)
932 {
933 mLastErrorFile = UString(_filename).asUTF8();
934 }
935
936} // namespace MyGUI
virtual void readline(std::string &_source, Char _delim='\n')=0
virtual bool eof()=0
A UTF-16 string with implicit conversion to/from std::string and std::wstring.
const std::wstring & asWStr() const
returns the current string in the native form of std::wstring
const std::string & asUTF8() const
returns the current string in UTF-8 form within a std::string
bool save(const std::string &_filename)
std::string getLastError() const
ElementPtr getRoot() const
ElementPtr createRoot(std::string_view _name)
ElementPtr createDeclaration(std::string_view _version="1.0", std::string_view _encoding="UTF-8")
bool open(const std::string &_filename)
const std::string & getContent() const
bool findAttribute(std::string_view _name, std::string &_value)
std::unique_ptr< Element > createCopy()
ElementEnumerator getElementEnumerator()
ElementType getType() const
void removeChild(ElementPtr _child)
ElementPtr getParent() const
Element(std::string_view _name, ElementPtr _parent, ElementType _type=ElementType::Normal, std::string_view _content={})
const std::string & getName() const
void addAttribute(std::string_view _key, const T &_value)
void setAttribute(std::string_view _key, std::string_view _value)
void removeAttribute(std::string_view _key)
void setContent(const T &_content)
void addContent(const T &_content)
const VectorAttributes & getAttributes() const
ElementPtr createChild(std::string_view _name, std::string_view _content={}, ElementType _type=ElementType::Normal)
std::string toString(T _value)
void trim(std::string &_str, bool _left=true, bool _right=true)
static std::string convert_from_xml(std::string_view _string, bool &_ok)
static std::string convert_to_xml(std::string_view _string)
std::vector< PairAttribute > VectorAttributes
void open_stream(std::ofstream &_stream, const std::wstring &_wide)
std::string_view print() const