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