Browse Source

Improve tinyXml2 output formatting

Lukas Aldershaab 3 years ago
parent
commit
165459c90b
2 changed files with 260 additions and 91 deletions
  1. 235 91
      Engine/source/persistence/taml/fsTinyXml.cpp
  2. 25 0
      Engine/source/persistence/taml/fsTinyXml.h

+ 235 - 91
Engine/source/persistence/taml/fsTinyXml.cpp

@@ -26,111 +26,126 @@
 
 #include "console/console.h"
 
-bool VfsXMLDocument::LoadFile(const char* pFilename)
-{
-   // Expand the file-path.
-   char filenameBuffer[1024];
-   Con::expandScriptFilename(filenameBuffer, sizeof(filenameBuffer), pFilename);
-
-   FileStream stream;
-
-#ifdef TORQUE_OS_ANDROID
-   if (strlen(pFilename) > strlen(filenameBuffer)) {
-      dStrcpy(filenameBuffer, pFilename, 1024);
-   }
-#endif
 
-   // File open for read?
-   if (!stream.open(filenameBuffer, Torque::FS::File::Read))
-   {
-      // No, so warn.
-      Con::warnf("TamlXmlParser::parse() - Could not open filename '%s' for parse.", filenameBuffer);
-      return false;
-   }
+// Re-implement private functionality in TinyXML2
+
+static const char LINE_FEED = static_cast<char>(0x0a);			// all line endings are normalized to LF
+static const char LF = LINE_FEED;
+static const char CARRIAGE_RETURN = static_cast<char>(0x0d);			// CR gets filtered out
+static const char CR = CARRIAGE_RETURN;
+static const char SINGLE_QUOTE = '\'';
+static const char DOUBLE_QUOTE = '\"';
+
+struct Entity {
+   const char* pattern;
+   int length;
+   char value;
+};
+
+static const int NUM_ENTITIES = 5;
+static const Entity entities[NUM_ENTITIES] = {
+    { "quot", 4,	DOUBLE_QUOTE },
+    { "amp", 3,		'&'  },
+    { "apos", 4,	SINGLE_QUOTE },
+    { "lt",	2, 		'<'	 },
+    { "gt",	2,		'>'	 }
+};
 
-   // Load document from stream.
-   if (!LoadFile(stream))
-   {
-      // Warn!
-      Con::warnf("TamlXmlParser: Could not load Taml XML file from stream.");
-      return false;
-   }
-
-   // Close the stream.
-   stream.close();
-   return true;
-}
-
-bool VfsXMLDocument::SaveFile(const char* pFilename)
+VfsXMLPrinter::VfsXMLPrinter(FileStream& stream, bool compact, int depth)
+   : XMLPrinter(NULL, compact, depth),
+     m_Stream(stream),
+     _depth(depth)
 {
-   // Expand the file-name into the file-path buffer.
-   char filenameBuffer[1024];
-   Con::expandScriptFilename(filenameBuffer, sizeof(filenameBuffer), pFilename);
-
-   FileStream stream;
-
-   // File opened?
-   if (!stream.open(filenameBuffer, Torque::FS::File::Write))
-   {
-      // No, so warn.
-      Con::warnf("Taml::writeFile() - Could not open filename '%s' for write.", filenameBuffer);
-      return false;
+   for (int i = 0; i < ENTITY_RANGE; ++i) {
+      _entityFlag[i] = false;
+      _restrictedEntityFlag[i] = false;
    }
-
-   bool ret = SaveFile(stream);
-
-   stream.close();
-   return ret;
+   for (int i = 0; i < NUM_ENTITIES; ++i) {
+      const char entityValue = entities[i].value;
+      const unsigned char flagIndex = static_cast<unsigned char>(entityValue);
+      TIXMLASSERT(flagIndex < ENTITY_RANGE);
+      _entityFlag[flagIndex] = true;
+   }
+   _restrictedEntityFlag[static_cast<unsigned char>('&')] = true;
+   _restrictedEntityFlag[static_cast<unsigned char>('<')] = true;
+   _restrictedEntityFlag[static_cast<unsigned char>('>')] = true;	// not required, but consistency is nice
 }
 
-void VfsXMLDocument::ClearError()
+VfsXMLPrinter::~VfsXMLPrinter()
 {
-   _errorID = tinyxml2::XML_SUCCESS;
-   _errorLineNum = 0;
-   _errorStr.Reset();
-
-   tinyxml2::XMLDocument::ClearError();
+   m_Stream.flush();
+   m_Stream.close();
 }
 
-void VfsXMLDocument::SetError(tinyxml2::XMLError error, int lineNum, const char* format, ...)
+void VfsXMLPrinter::PrintString(const char* p, bool restricted)
 {
-   TIXMLASSERT(error >= 0 && error < tinyxml2::XML_ERROR_COUNT);
-   _errorID = error;
-   _errorLineNum = lineNum;
-   _errorStr.Reset();
-
-   const size_t BUFFER_SIZE = 1000;
-   char* buffer = new char[BUFFER_SIZE];
-
-   TIXMLASSERT(sizeof(error) <= sizeof(int));
-   dSprintf(buffer, BUFFER_SIZE, "Error=%s ErrorID=%d (0x%x) Line number=%d", ErrorIDToName(error), int(error), int(error), lineNum);
-
-   if (format) {
-      size_t len = strlen(buffer);
-      dSprintf(buffer + len, BUFFER_SIZE - len, ": ");
-      len = strlen(buffer);
-
-      va_list va;
-      va_start(va, format);
-      dSprintf(buffer + len, BUFFER_SIZE - len, format, va);
-      va_end(va);
+   // Look for runs of bytes between entities to print.
+   const char* q = p;
+
+   if (_processEntities) {
+      const bool* flag = restricted ? _restrictedEntityFlag : _entityFlag;
+      while (*q) {
+         TIXMLASSERT(p <= q);
+         // Remember, char is sometimes signed. (How many times has that bitten me?)
+         if (*q > 0 && *q < ENTITY_RANGE) {
+            // Check for entities. If one is found, flush
+            // the stream up until the entity, write the
+            // entity, and keep looking.
+            if (flag[static_cast<unsigned char>(*q)]) {
+               while (p < q) {
+                  const size_t delta = q - p;
+                  const int toPrint = (INT_MAX < delta) ? INT_MAX : static_cast<int>(delta);
+                  Write(p, toPrint);
+                  p += toPrint;
+               }
+               bool entityPatternPrinted = false;
+               for (int i = 0; i < NUM_ENTITIES; ++i) {
+                  if (entities[i].value == *q) {
+                     Putc('&');
+                     Write(entities[i].pattern, entities[i].length);
+                     Putc(';');
+                     entityPatternPrinted = true;
+                     break;
+                  }
+               }
+               if (!entityPatternPrinted) {
+                  // TIXMLASSERT( entityPatternPrinted ) causes gcc -Wunused-but-set-variable in release
+                  TIXMLASSERT(false);
+               }
+               ++p;
+            }
+         }
+         ++q;
+         TIXMLASSERT(p <= q);
+      }
+      // Flush the remaining string. This will be the entire
+      // string if an entity wasn't found.
+      if (p < q) {
+         const size_t delta = q - p;
+         const int toPrint = (INT_MAX < delta) ? INT_MAX : static_cast<int>(delta);
+         Write(p, toPrint);
+      }
+   }
+   else {
+      Write(p);
    }
-   _errorStr.SetStr(buffer);
-   delete[] buffer;
 }
 
-VfsXMLPrinter::VfsXMLPrinter(FileStream& stream, bool compact, int depth)
-   : XMLPrinter(NULL, compact, depth),
-     m_Stream(stream)
+bool VfsXMLPrinter::VisitEnter(const tinyxml2::XMLDocument& doc)
 {
+   _processEntities = doc.ProcessEntities();
+   return XMLPrinter::VisitEnter(doc);
 }
 
-VfsXMLPrinter::~VfsXMLPrinter()
+bool VfsXMLPrinter::VisitExit(const tinyxml2::XMLElement& element)
 {
-   m_Stream.flush();
-   m_Stream.close();
+   _depth--;
+   return XMLPrinter::VisitExit(element);
 }
 
+
+// Add VFS friendly implementations of output functions
+
 void VfsXMLPrinter::Print(const char* format, ...)
 {
    va_list     va;
@@ -151,6 +166,77 @@ void VfsXMLPrinter::Putc(char ch)
    m_Stream.write(static_cast<U8>(ch));
 }
 
+// Overwrite Visitation of elements to add newlines before attributes
+
+bool VfsXMLPrinter::VisitEnter(const tinyxml2::XMLElement& element, const tinyxml2::XMLAttribute* attribute)
+{
+   const tinyxml2::XMLElement* parentElem = 0;
+   if (element.Parent()) {
+      parentElem = element.Parent()->ToElement();
+   }
+   const bool compactMode = parentElem ? CompactMode(*parentElem) : CompactMode(element);
+   OpenElement(element.Name(), compactMode);
+   _depth++;
+   while (attribute) {
+      PushAttribute(attribute->Name(), attribute->Value(), compactMode);
+      attribute = attribute->Next();
+   }
+   return true;
+}
+
+void VfsXMLPrinter::PushAttribute(const char* name, const char* value, bool compactMode)
+{
+   TIXMLASSERT(_elementJustOpened);
+   if (compactMode)
+   {
+      Putc(' ');
+   }
+   else
+   {
+      Putc('\n');
+      PrintSpace(_depth);
+   }
+   Write(name);
+   Write("=\"");
+   PrintString(value, false);
+   Putc('\"');
+}
+
+bool VfsXMLDocument::LoadFile(const char* pFilename)
+{
+   // Expand the file-path.
+   char filenameBuffer[1024];
+   Con::expandScriptFilename(filenameBuffer, sizeof(filenameBuffer), pFilename);
+
+   FileStream stream;
+
+#ifdef TORQUE_OS_ANDROID
+   if (strlen(pFilename) > strlen(filenameBuffer)) {
+      dStrcpy(filenameBuffer, pFilename, 1024);
+   }
+#endif
+
+   // File open for read?
+   if (!stream.open(filenameBuffer, Torque::FS::File::Read))
+   {
+      // No, so warn.
+      Con::warnf("TamlXmlParser::parse() - Could not open filename '%s' for parse.", filenameBuffer);
+      return false;
+   }
+
+   // Load document from stream.
+   if (!LoadFile(stream))
+   {
+      // Warn!
+      Con::warnf("TamlXmlParser: Could not load Taml XML file from stream.");
+      return false;
+   }
+
+   // Close the stream.
+   stream.close();
+   return true;
+}
+
 bool VfsXMLDocument::LoadFile(FileStream& stream)
 {
    // Delete the existing data:
@@ -196,7 +282,7 @@ bool VfsXMLDocument::LoadFile(FileStream& stream)
 
    if (!stream.read(length, buf))
    {
-      delete [] buf;
+      delete[] buf;
       SetError(tinyxml2::XML_ERROR_FILE_COULD_NOT_BE_OPENED, 0, 0);
       return false;
    }
@@ -220,8 +306,8 @@ bool VfsXMLDocument::LoadFile(FileStream& stream)
    buf[length] = 0;
    while (*p)
    {
-      assert(p < (buf+length));
-      assert(q <= (buf+length));
+      assert(p < (buf + length));
+      assert(q <= (buf + length));
       assert(q <= p);
 
       if (*p == CR)
@@ -239,12 +325,12 @@ bool VfsXMLDocument::LoadFile(FileStream& stream)
          *q++ = *p++;
       }
    }
-   assert(q <= (buf+length));
+   assert(q <= (buf + length));
    *q = 0;
 
    Parse(buf, length);
 
-   delete [] buf;
+   delete[] buf;
    return !Error();
 }
 
@@ -257,3 +343,61 @@ bool VfsXMLDocument::SaveFile(FileStream& stream)
    Print(&printer);
    return !Error();
 }
+
+bool VfsXMLDocument::SaveFile(const char* pFilename)
+{
+   // Expand the file-name into the file-path buffer.
+   char filenameBuffer[1024];
+   Con::expandScriptFilename(filenameBuffer, sizeof(filenameBuffer), pFilename);
+
+   FileStream stream;
+
+   // File opened?
+   if (!stream.open(filenameBuffer, Torque::FS::File::Write))
+   {
+      // No, so warn.
+      Con::warnf("Taml::writeFile() - Could not open filename '%s' for write.", filenameBuffer);
+      return false;
+   }
+
+   bool ret = SaveFile(stream);
+
+   stream.close();
+   return ret;
+}
+
+void VfsXMLDocument::ClearError()
+{
+   _errorID = tinyxml2::XML_SUCCESS;
+   _errorLineNum = 0;
+   _errorStr.Reset();
+
+   tinyxml2::XMLDocument::ClearError();
+}
+
+void VfsXMLDocument::SetError(tinyxml2::XMLError error, int lineNum, const char* format, ...)
+{
+   TIXMLASSERT(error >= 0 && error < tinyxml2::XML_ERROR_COUNT);
+   _errorID = error;
+   _errorLineNum = lineNum;
+   _errorStr.Reset();
+
+   const size_t BUFFER_SIZE = 1000;
+   char* buffer = new char[BUFFER_SIZE];
+
+   TIXMLASSERT(sizeof(error) <= sizeof(int));
+   dSprintf(buffer, BUFFER_SIZE, "Error=%s ErrorID=%d (0x%x) Line number=%d", ErrorIDToName(error), int(error), int(error), lineNum);
+
+   if (format) {
+      size_t len = strlen(buffer);
+      dSprintf(buffer + len, BUFFER_SIZE - len, ": ");
+      len = strlen(buffer);
+
+      va_list va;
+      va_start(va, format);
+      dSprintf(buffer + len, BUFFER_SIZE - len, format, va);
+      va_end(va);
+   }
+   _errorStr.SetStr(buffer);
+   delete[] buffer;
+}

+ 25 - 0
Engine/source/persistence/taml/fsTinyXml.h

@@ -40,10 +40,35 @@ public:
    VfsXMLPrinter(FileStream& stream, bool compact = false, int depth = 0);
    ~VfsXMLPrinter() override;
 
+   // Re-implement private functionality in TinyXML2 library, this is just a copy-paste job
+   void PrintString(const char*, bool restrictedEntitySet);	// prints out, after detecting entities.
+
+   virtual bool VisitEnter(const tinyxml2::XMLDocument& /*doc*/);
+   virtual bool VisitExit(const tinyxml2::XMLElement& element);
+
+   // Add VFS friendly implementations of output functions
    void Print(const char* format, ...) override;
    void Write(const char* data, size_t size) override;
+   inline void Write(const char* data) { Write(data, strlen(data)); }
    void Putc(char ch) override;
+
+   // Overwrite Visitation of elements to add newlines before attributes
+   virtual bool VisitEnter(const tinyxml2::XMLElement& element, const tinyxml2::XMLAttribute* attribute);
+   void PushAttribute(const char* name, const char* value, bool compactMode);
+
+   // Accept a virtual FileStream instead of a FILE pointer
    FileStream& m_Stream;
+
+   // Track private fields that are necessary for private functionality in TinyXML2
+   int _depth;
+   bool _processEntities;
+
+   enum {
+      ENTITY_RANGE = 64,
+      BUF_SIZE = 200
+   };
+   bool _entityFlag[ENTITY_RANGE];
+   bool _restrictedEntityFlag[ENTITY_RANGE];
 };
 
 class VfsXMLDocument : public tinyxml2::XMLDocument