fsTinyXml.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2013 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "fsTinyXml.h"
  23. #include <cassert>
  24. #include "console/console.h"
  25. VfsXMLPrinter::VfsXMLPrinter(FileStream& stream, bool compact, int depth)
  26. : XMLPrinter(NULL, compact, depth),
  27. m_Stream(stream)
  28. {
  29. }
  30. VfsXMLPrinter::~VfsXMLPrinter()
  31. {
  32. m_Stream.flush();
  33. m_Stream.close();
  34. }
  35. // Add VFS friendly implementations of output functions
  36. void VfsXMLPrinter::Print(const char* format, ...)
  37. {
  38. va_list va;
  39. va_start(va, format);
  40. m_Stream.writeFormattedBuffer(format, va);
  41. va_end(va);
  42. }
  43. void VfsXMLPrinter::Write(const char* data, size_t size)
  44. {
  45. m_Stream.write(size, data);
  46. }
  47. void VfsXMLPrinter::Putc(char ch)
  48. {
  49. m_Stream.write(static_cast<U8>(ch));
  50. }
  51. bool VfsXMLDocument::LoadFile(const char* pFilename)
  52. {
  53. // Expand the file-path.
  54. char filenameBuffer[1024];
  55. Con::expandScriptFilename(filenameBuffer, sizeof(filenameBuffer), pFilename);
  56. FileStream stream;
  57. #ifdef TORQUE_OS_ANDROID
  58. if (strlen(pFilename) > strlen(filenameBuffer)) {
  59. dStrcpy(filenameBuffer, pFilename, 1024);
  60. }
  61. #endif
  62. // File open for read?
  63. if (!stream.open(filenameBuffer, Torque::FS::File::Read))
  64. {
  65. // No, so warn.
  66. Con::warnf("TamlXmlParser::parse() - Could not open filename '%s' for parse.", filenameBuffer);
  67. return false;
  68. }
  69. // Load document from stream.
  70. if (!LoadFile(stream))
  71. {
  72. // Warn!
  73. Con::warnf("TamlXmlParser: Could not load Taml XML file from stream.");
  74. return false;
  75. }
  76. // Close the stream.
  77. stream.close();
  78. return true;
  79. }
  80. bool VfsXMLDocument::LoadFile(FileStream& stream)
  81. {
  82. // Delete the existing data:
  83. Clear();
  84. // Clear shadowed error
  85. ClearError();
  86. //TODO: Can't clear location, investigate if this gives issues.
  87. //doc.location.Clear();
  88. // Get the file size, so we can pre-allocate the string. HUGE speed impact.
  89. long length = stream.getStreamSize();
  90. // Strange case, but good to handle up front.
  91. if (length <= 0)
  92. {
  93. SetError(tinyxml2::XML_ERROR_EMPTY_DOCUMENT, 0, 0);
  94. return false;
  95. }
  96. // Subtle bug here. TinyXml did use fgets. But from the XML spec:
  97. // 2.11 End-of-Line Handling
  98. // <snip>
  99. // <quote>
  100. // ...the XML processor MUST behave as if it normalized all line breaks in external
  101. // parsed entities (including the document entity) on input, before parsing, by translating
  102. // both the two-character sequence #xD #xA and any #xD that is not followed by #xA to
  103. // a single #xA character.
  104. // </quote>
  105. //
  106. // It is not clear fgets does that, and certainly isn't clear it works cross platform.
  107. // Generally, you expect fgets to translate from the convention of the OS to the c/unix
  108. // convention, and not work generally.
  109. /*
  110. while( fgets( buf, sizeof(buf), file ) )
  111. {
  112. data += buf;
  113. }
  114. */
  115. char* buf = new char[length + 1];
  116. buf[0] = 0;
  117. if (!stream.read(length, buf))
  118. {
  119. delete[] buf;
  120. SetError(tinyxml2::XML_ERROR_FILE_COULD_NOT_BE_OPENED, 0, 0);
  121. return false;
  122. }
  123. // Process the buffer in place to normalize new lines. (See comment above.)
  124. // Copies from the 'p' to 'q' pointer, where p can advance faster if
  125. // a newline-carriage return is hit.
  126. //
  127. // Wikipedia:
  128. // Systems based on ASCII or a compatible character set use either LF (Line feed, '\n', 0x0A, 10 in decimal) or
  129. // CR (Carriage return, '\r', 0x0D, 13 in decimal) individually, or CR followed by LF (CR+LF, 0x0D 0x0A)...
  130. // * LF: Multics, Unix and Unix-like systems (GNU/Linux, AIX, Xenix, Mac OS X, FreeBSD, etc.), BeOS, Amiga, RISC OS, and others
  131. // * CR+LF: DEC RT-11 and most other early non-Unix, non-IBM OSes, CP/M, MP/M, DOS, OS/2, Microsoft Windows, Symbian OS
  132. // * CR: Commodore 8-bit machines, Apple II family, Mac OS up to version 9 and OS-9
  133. const char* p = buf; // the read head
  134. char* q = buf; // the write head
  135. const char CR = 0x0d;
  136. const char LF = 0x0a;
  137. buf[length] = 0;
  138. while (*p)
  139. {
  140. assert(p < (buf + length));
  141. assert(q <= (buf + length));
  142. assert(q <= p);
  143. if (*p == CR)
  144. {
  145. *q++ = LF;
  146. p++;
  147. if (*p == LF)
  148. {
  149. // check for CR+LF (and skip LF)
  150. p++;
  151. }
  152. }
  153. else
  154. {
  155. *q++ = *p++;
  156. }
  157. }
  158. assert(q <= (buf + length));
  159. *q = 0;
  160. Parse(buf, length);
  161. delete[] buf;
  162. return !Error();
  163. }
  164. bool VfsXMLDocument::SaveFile(FileStream& stream)
  165. {
  166. // Clear any error from the last save, otherwise it will get reported
  167. // for *this* call.
  168. ClearError();
  169. VfsXMLPrinter printer(stream, false, 0);
  170. PrettyXMLPrinter prettyPrinter(printer);
  171. Print(&prettyPrinter);
  172. return !Error();
  173. }
  174. bool VfsXMLDocument::SaveFile(const char* pFilename)
  175. {
  176. // Expand the file-name into the file-path buffer.
  177. char filenameBuffer[1024];
  178. Con::expandScriptFilename(filenameBuffer, sizeof(filenameBuffer), pFilename);
  179. FileStream stream;
  180. // File opened?
  181. if (!stream.open(filenameBuffer, Torque::FS::File::Write))
  182. {
  183. // No, so warn.
  184. Con::warnf("Taml::writeFile() - Could not open filename '%s' for write.", filenameBuffer);
  185. return false;
  186. }
  187. bool ret = SaveFile(stream);
  188. stream.close();
  189. return ret;
  190. }
  191. void VfsXMLDocument::ClearError()
  192. {
  193. _errorID = tinyxml2::XML_SUCCESS;
  194. _errorLineNum = 0;
  195. _errorStr.Reset();
  196. tinyxml2::XMLDocument::ClearError();
  197. }
  198. void VfsXMLDocument::SetError(tinyxml2::XMLError error, int lineNum, const char* format, ...)
  199. {
  200. TIXMLASSERT(error >= 0 && error < tinyxml2::XML_ERROR_COUNT);
  201. _errorID = error;
  202. _errorLineNum = lineNum;
  203. _errorStr.Reset();
  204. const size_t BUFFER_SIZE = 1000;
  205. char* buffer = new char[BUFFER_SIZE];
  206. TIXMLASSERT(sizeof(error) <= sizeof(int));
  207. dSprintf(buffer, BUFFER_SIZE, "Error=%s ErrorID=%d (0x%x) Line number=%d", ErrorIDToName(error), int(error), int(error), lineNum);
  208. if (format) {
  209. size_t len = strlen(buffer);
  210. dSprintf(buffer + len, BUFFER_SIZE - len, ": ");
  211. len = strlen(buffer);
  212. va_list va;
  213. va_start(va, format);
  214. dSprintf(buffer + len, BUFFER_SIZE - len, format, va);
  215. va_end(va);
  216. }
  217. _errorStr.SetStr(buffer);
  218. delete[] buffer;
  219. }
  220. // Overwrite Visitation of elements to add newlines before attributes
  221. PrettyXMLPrinter::PrettyXMLPrinter(VfsXMLPrinter& innerPrinter, int depth)
  222. : mInnerPrinter(innerPrinter),
  223. mDepth(depth)
  224. {
  225. for (int i = 0; i < ENTITY_RANGE; ++i) {
  226. mEntityFlag[i] = false;
  227. mRestrictedEntityFlag[i] = false;
  228. }
  229. for (int i = 0; i < NUM_ENTITIES; ++i) {
  230. const char entityValue = entities[i].value;
  231. const unsigned char flagIndex = static_cast<unsigned char>(entityValue);
  232. TIXMLASSERT(flagIndex < ENTITY_RANGE);
  233. mEntityFlag[flagIndex] = true;
  234. }
  235. mRestrictedEntityFlag[static_cast<unsigned char>('&')] = true;
  236. mRestrictedEntityFlag[static_cast<unsigned char>('<')] = true;
  237. mRestrictedEntityFlag[static_cast<unsigned char>('>')] = true; // not required, but consistency is nice
  238. }
  239. void PrettyXMLPrinter::PrintString(const char* p, bool restricted)
  240. {
  241. // Look for runs of bytes between entities to print.
  242. const char* q = p;
  243. if (mProcessEntities) {
  244. const bool* flag = restricted ? mRestrictedEntityFlag : mEntityFlag;
  245. while (*q) {
  246. TIXMLASSERT(p <= q);
  247. // Remember, char is sometimes signed. (How many times has that bitten me?)
  248. if (*q > 0 && *q < ENTITY_RANGE) {
  249. // Check for entities. If one is found, flush
  250. // the stream up until the entity, write the
  251. // entity, and keep looking.
  252. if (flag[static_cast<unsigned char>(*q)]) {
  253. while (p < q) {
  254. const size_t delta = q - p;
  255. const int toPrint = (INT_MAX < delta) ? INT_MAX : static_cast<int>(delta);
  256. mInnerPrinter.Write(p, toPrint);
  257. p += toPrint;
  258. }
  259. bool entityPatternPrinted = false;
  260. for (int i = 0; i < NUM_ENTITIES; ++i) {
  261. if (entities[i].value == *q) {
  262. mInnerPrinter.Putc('&');
  263. mInnerPrinter.Write(entities[i].pattern, entities[i].length);
  264. mInnerPrinter.Putc(';');
  265. entityPatternPrinted = true;
  266. break;
  267. }
  268. }
  269. if (!entityPatternPrinted) {
  270. // TIXMLASSERT( entityPatternPrinted ) causes gcc -Wunused-but-set-variable in release
  271. TIXMLASSERT(false);
  272. }
  273. ++p;
  274. }
  275. }
  276. ++q;
  277. TIXMLASSERT(p <= q);
  278. }
  279. // Flush the remaining string. This will be the entire
  280. // string if an entity wasn't found.
  281. if (p < q) {
  282. const size_t delta = q - p;
  283. const int toPrint = (INT_MAX < delta) ? INT_MAX : static_cast<int>(delta);
  284. mInnerPrinter.Write(p, toPrint);
  285. }
  286. }
  287. else {
  288. mInnerPrinter.Write(p);
  289. }
  290. }
  291. bool PrettyXMLPrinter::VisitEnter(const tinyxml2::XMLElement& element, const tinyxml2::XMLAttribute* attribute)
  292. {
  293. const tinyxml2::XMLElement* parentElem = 0;
  294. if (element.Parent()) {
  295. parentElem = element.Parent()->ToElement();
  296. }
  297. const bool compactMode = parentElem ? mInnerPrinter.CompactMode(*parentElem) : mInnerPrinter.CompactMode(element);
  298. mInnerPrinter.OpenElement(element.Name(), compactMode);
  299. mDepth++;
  300. while (attribute) {
  301. PushAttribute(attribute->Name(), attribute->Value(), compactMode);
  302. attribute = attribute->Next();
  303. }
  304. return true;
  305. }
  306. void PrettyXMLPrinter::PushAttribute(const char* name, const char* value, bool compactMode)
  307. {
  308. if (compactMode)
  309. {
  310. mInnerPrinter.Putc(' ');
  311. }
  312. else
  313. {
  314. mInnerPrinter.Putc('\n');
  315. mInnerPrinter.PrintSpace(mDepth);
  316. }
  317. mInnerPrinter.Write(name);
  318. mInnerPrinter.Write("=\"");
  319. PrintString(value, false);
  320. mInnerPrinter.Putc('\"');
  321. }