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