MyGUI  3.2.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 
11 namespace 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(0),
387  mDeclaration(0),
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 = 0;
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  if (read.empty())
521  continue;
522  // текущая строка для разбора и то что еще прочитали
523  line += read;
524 
525  if (!parseLine(line, currentNode))
526  {
527  return false;
528  }
529 
530  } // while (!stream.eof())
531 
532  if (currentNode)
533  {
534  mLastError = ErrorType::NotClosedElements;
535  return false;
536  }
537 
538  return true;
539  }
540 
541  bool Document::save(std::ostream& _stream)
542  {
543  if (!mDeclaration)
544  {
545  mLastError = ErrorType::NoXMLDeclaration;
546  return false;
547  }
548 
549  // заголовок utf8
550  _stream << (char)0xEFu;
551  _stream << (char)0xBBu;
552  _stream << (char)0xBFu;
553 
554  mDeclaration->save(_stream, 0);
555  if (mRoot)
556  mRoot->save(_stream, 0);
557 
558  return true;
559  }
560 
562  {
563  clearDeclaration();
564  clearRoot();
565  mLine = 0;
566  mCol = 0;
567  }
568 
569  bool Document::parseTag(ElementPtr& _currentNode, std::string _content)
570  {
571  // убераем лишнее
572  MyGUI::utility::trim(_content);
573 
574  if (_content.empty())
575  {
576  // создаем пустой тег
577  if (_currentNode)
578  {
579  _currentNode = _currentNode->createChild("");
580  }
581  else
582  {
583  _currentNode = new Element("", 0);
584  // если это первый то запоминаем
585  if (!mRoot)
586  mRoot = _currentNode;
587  }
588  return true;
589  }
590 
591  char simbol = _content[0];
592  bool tagDeclaration = false;
593 
594  // проверяем на коментарии
595  if (simbol == '!')
596  {
597  if (_currentNode != 0)
598  {
599  //_currentNode->createChild("", _content, ElementType::Comment);
600  }
601  return true;
602  }
603  // проверяем на информационный тег
604  else if (simbol == '?')
605  {
606  tagDeclaration = true;
607  _content.erase(0, 1); // удаляем первый символ
608  }
609 
610  size_t start = 0;
611  size_t end = 0;
612  // проверяем на закрытие тега
613  if (simbol == '/')
614  {
615  if (_currentNode == 0)
616  {
617  // чета мы закрывам а ниче даже и не открыто
618  if (!mRoot)
619  {
621  return false;
622  }
623  }
624  // обрезаем имя тэга
625  start = _content.find_first_not_of(" \t", 1);
626  if (start == _content.npos)
627  {
628  // тег пустой
629  _content.clear();
630  }
631  else
632  {
633  end = _content.find_last_not_of(" \t");
634  _content = _content.substr(start, end - start + 1);
635  }
636  // проверяем соответствие открывающего и закрывающего тегов
637  if (_currentNode->getName() != _content)
638  {
640  return false;
641  }
642  // а теперь снижаем текущий узел вниз
643  _currentNode = _currentNode->getParent();
644  }
645  else
646  {
647  // выделяем имя до первого пробела или закрывающего тега
648  std::string cut = _content;
649  start = _content.find_first_of(" \t/?", 1); // << превед
650  if (start != _content.npos)
651  {
652  cut = _content.substr(0, start);
653  _content = _content.substr(start);
654  }
655  else
656  {
657  _content.clear();
658  }
659 
660  if (_currentNode)
661  {
662  _currentNode = _currentNode->createChild(cut);
663  }
664  else
665  {
666  if (tagDeclaration)
667  {
668  // информационный тег
669  if (mDeclaration)
670  {
672  return false;
673  }
674  _currentNode = new Element(cut, 0, ElementType::Declaration);
675  mDeclaration = _currentNode;
676  }
677  else
678  {
679  // рутовый тег
680  if (mRoot)
681  {
683  return false;
684  }
685  _currentNode = new Element(cut, 0, ElementType::Normal);
686  mRoot = _currentNode;
687  }
688  }
689 
690  // проверим на пустоту
691  start = _content.find_last_not_of(" \t");
692  if (start == _content.npos)
693  return true;
694 
695  // сразу отделим закрывающийся тэг
696  bool close = false;
697  if ((_content[start] == '/') || (_content[start] == '?'))
698  {
699  close = true;
700  // не будем резать строку, просто поставим пробел
701  _content[start] = ' ';
702  // проверим на пустоту
703  start = _content.find_last_not_of(" \t");
704  if (start == _content.npos)
705  {
706  // возвращаем все назад и уходим
707  _currentNode = _currentNode->getParent();
708  return true;
709  }
710  }
711 
712  // а вот здесь уже в цикле разбиваем на атрибуты
713  while (true)
714  {
715  // ищем равно
716  start = _content.find('=');
717  if (start == _content.npos)
718  {
719  mLastError = ErrorType::IncorrectAttribute;
720  return false;
721  }
722  // ищем вторые ковычки
723  end = _content.find_first_of("\"\'", start + 1);
724  if (end == _content.npos)
725  {
726  mLastError = ErrorType::IncorrectAttribute;
727  return false;
728  }
729  end = _content.find_first_of("\"\'", end + 1);
730  if (end == _content.npos)
731  {
732  mLastError = ErrorType::IncorrectAttribute;
733  return false;
734  }
735 
736  std::string key = _content.substr(0, start);
737  std::string value = _content.substr(start + 1, end - start);
738 
739  // проверка на валидность
740  if (! checkPair(key, value))
741  {
742  mLastError = ErrorType::IncorrectAttribute;
743  return false;
744  }
745 
746  // добавляем пару в узел
747  _currentNode->addAttribute(key, value);
748 
749  // следующий кусок
750  _content = _content.substr(end + 1);
751 
752  // в строке не осталось символов
753  start = _content.find_first_not_of(" \t");
754  if (start == _content.npos)
755  break;
756 
757  mCol += start;
758  }
759 
760  // был закрывающий тег для текущего тега
761  if (close)
762  {
763  // не проверяем имена, потому что это наш тэг
764  _currentNode = _currentNode->getParent();
765  }
766 
767  }
768  return true;
769  }
770 
771  bool Document::checkPair(std::string& _key, std::string& _value)
772  {
773  // в ключе не должно быть ковычек и пробелов
774  MyGUI::utility::trim(_key);
775  if (_key.empty())
776  return false;
777  size_t start = _key.find_first_of(" \t\"\'&");
778  if (start != _key.npos)
779  return false;
780 
781  // в значении, ковычки по бокам
782  MyGUI::utility::trim(_value);
783  if (_value.size() < 2)
784  return false;
785  if (((_value[0] != '"') || (_value[_value.length() - 1] != '"')) &&
786  ((_value[0] != '\'') || (_value[_value.length() - 1] != '\'')))
787  return false;
788  bool ok = true;
789  _value = utility::convert_from_xml(_value.substr(1, _value.length() - 2), ok);
790  return ok;
791  }
792 
793  // ищет символ без учета ковычек
794  size_t Document::find(const std::string& _text, char _char, size_t _start)
795  {
796  // ковычки
797  bool kov = false;
798 
799  // буфер для поиска
800  char buff[16] = "\"_\0";
801  buff[1] = _char;
802 
803  size_t pos = _start;
804 
805  while (true)
806  {
807  pos = _text.find_first_of(buff, pos);
808 
809  // если уже конец, то досвидания
810  if (pos == _text.npos)
811  {
812  break;
813  }
814  // нашли ковычку
815  else if (_text[pos] == '"')
816  {
817  kov = !kov;
818  pos ++;
819  }
820  // если мы в ковычках, то идем дальше
821  else if (kov)
822  {
823  pos ++;
824  }
825  // мы не в ковычках
826  else
827  {
828  break;
829  }
830  }
831 
832  return pos;
833  }
834 
835  void Document::clearDeclaration()
836  {
837  if (mDeclaration)
838  {
839  delete mDeclaration;
840  mDeclaration = 0;
841  }
842  }
843 
844  void Document::clearRoot()
845  {
846  if (mRoot)
847  {
848  delete mRoot;
849  mRoot = 0;
850  }
851  }
852 
853  ElementPtr Document::createDeclaration(const std::string& _version, const std::string& _encoding)
854  {
855  clearDeclaration();
856  mDeclaration = new Element("xml", 0, ElementType::Declaration);
857  mDeclaration->addAttribute("version", _version);
858  mDeclaration->addAttribute("encoding", _encoding);
859  return mDeclaration;
860  }
861 
862  ElementPtr Document::createRoot(const std::string& _name)
863  {
864  clearRoot();
865  mRoot = new Element(_name, 0, ElementType::Normal);
866  return mRoot;
867  }
868 
869  bool Document::parseLine(std::string& _line, ElementPtr& _element)
870  {
871  // крутимся пока в строке есть теги
872  while (true)
873  {
874  // сначала ищем по угловым скобкам
875  size_t start = find(_line, '<');
876  if (start == _line.npos)
877  break;
878  size_t end = _line.npos;
879 
880  // пытаемся вырезать многострочный коментарий
881  if ((start + 3 < _line.size()) && (_line[start + 1] == '!') && (_line[start + 2] == '-') && (_line[start + 3] == '-'))
882  {
883  end = _line.find("-->", start + 4);
884  if (end == _line.npos)
885  break;
886  end += 2;
887  }
888  else
889  {
890  end = find(_line, '>', start + 1);
891  if (end == _line.npos)
892  break;
893  }
894  // проверяем на наличее тела
895  size_t body = _line.find_first_not_of(" \t<");
896  if (body < start)
897  {
898  std::string body_str = _line.substr(0, start);
899  // текущий символ
900  mCol = 0;
901 
902  if (_element != 0)
903  {
904  bool ok = true;
905  _element->setContent(utility::convert_from_xml(body_str, ok));
906  if (!ok)
907  {
908  mLastError = ErrorType::IncorrectContent;
909  return false;
910  }
911  }
912  }
913  // вырезаем наш тэг и парсим
914  if (!parseTag(_element, _line.substr(start + 1, end - start - 1)))
915  {
916  return false;
917  }
918  // и обрезаем текущую строку разбора
919  _line = _line.substr(end + 1);
920  }
921  return true;
922  }
923 
925  {
926  const std::string& error = mLastError.print();
927  if (error.empty())
928  return error;
929  return MyGUI::utility::toString("'", error, "' , file='", mLastErrorFile, "' , line=", mLine, " , col=", mCol);
930  }
931 
932  bool Document::open(const UString& _filename)
933  {
934  return open(_filename.asWStr());
935  }
936 
937  bool Document::save(const UString& _filename)
938  {
939  return save(_filename.asWStr());
940  }
941 
943  {
944  mLastError = ErrorType::MAX;
945  }
946 
948  {
949  return mRoot;
950  }
951 
952  void Document::setLastFileError(const std::string& _filename)
953  {
954  mLastErrorFile = _filename;
955  }
956 
957  void Document::setLastFileError(const std::wstring& _filename)
958  {
959  mLastErrorFile = UString(_filename).asUTF8();
960  }
961 
962  } // namespace xml
963 
964 } // namespace MyGUI