]> de.git.xonotic.org Git - xonotic/netradiant.git/blobdiff - libs/xml/xmltextags.cpp
fix lots of CRLFs
[xonotic/netradiant.git] / libs / xml / xmltextags.cpp
index 4ab2c6c3c0171a489b1fe3aa9dbb4660c1879bf1..c30f986795e22f404bb506e24566166635281171 100644 (file)
-/*\r
-Copyright (C) 2006, Stefan Greven.\r
-All Rights Reserved.\r
-\r
-This file is part of GtkRadiant.\r
-\r
-GtkRadiant is free software; you can redistribute it and/or modify\r
-it under the terms of the GNU General Public License as published by\r
-the Free Software Foundation; either version 2 of the License, or\r
-(at your option) any later version.\r
-\r
-GtkRadiant is distributed in the hope that it will be useful,\r
-but WITHOUT ANY WARRANTY; without even the implied warranty of\r
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
-GNU General Public License for more details.\r
-\r
-You should have received a copy of the GNU General Public License\r
-along with GtkRadiant; if not, write to the Free Software\r
-Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\r
-*/\r
-\r
-#include "xmltextags.h"\r
-\r
-#include <string>\r
-\r
-#include "qerplugin.h"\r
-#include "stream/stringstream.h"\r
-\r
-XmlTagBuilder::XmlTagBuilder()\r
-{\r
-}\r
-\r
-XmlTagBuilder::~XmlTagBuilder()\r
-{\r
-  // clean up\r
-  xmlFreeDoc(doc);\r
-  xmlXPathFreeContext(context);\r
-}\r
-\r
-bool XmlTagBuilder::CreateXmlDocument()\r
-{\r
-  /* Creates an XML file\r
-\r
-     returns TRUE if the file was created successfully or FALSE when failed\r
-  */\r
-\r
-  xmlTextWriterPtr writer;\r
-\r
-  writer = xmlNewTextWriterDoc(&doc, 0);\r
-\r
-  // begin a new UTF-8 formatted xml document\r
-  xmlTextWriterStartDocument(writer, NULL, "UTF-8", NULL);\r
-\r
-  // create the root node with stock and custom elements\r
-  xmlTextWriterStartElement(writer, (xmlChar*)"root");\r
-  xmlTextWriterWriteString(writer, (xmlChar*)"\n  ");\r
-  xmlTextWriterStartElement(writer, (xmlChar*)"stock");\r
-  xmlTextWriterWriteString(writer, (xmlChar*)"\n  ");\r
-  xmlTextWriterEndElement(writer);\r
-  xmlTextWriterWriteString(writer, (xmlChar*)"\n  ");\r
-  xmlTextWriterStartElement(writer, (xmlChar*)"custom");\r
-  xmlTextWriterWriteString(writer, (xmlChar*)"\n  ");\r
-  xmlTextWriterEndElement(writer);\r
-  xmlTextWriterWriteString(writer, (xmlChar*)"\n");\r
-  xmlTextWriterEndElement(writer);\r
-\r
-  // end of the xml document\r
-  xmlTextWriterEndDocument(writer);\r
-  xmlFreeTextWriter(writer);\r
-\r
-  if(!doc)\r
-  {\r
-    return false;\r
-  } else {\r
-    context = xmlXPathNewContext(doc);\r
-       return true;\r
-  }\r
-}\r
-\r
-bool XmlTagBuilder::OpenXmlDoc(const char* file, const char* savefile)\r
-{\r
-  /* Reads a XML document from a file\r
-\r
-     returns TRUE if the document was read successfully or FALSE when failed\r
-  */\r
-\r
-  if(savefile)\r
-    m_savefilename = savefile;\r
-  else\r
-    m_savefilename = file;\r
-    \r
-  doc = xmlParseFile(file);    // TODO error checking!\r
-\r
-  if(!doc)\r
-  {\r
-    return false;\r
-  } else {\r
-    context = xmlXPathNewContext(doc);\r
-       return true;\r
-  }\r
-}\r
-\r
-bool XmlTagBuilder::SaveXmlDoc(void)\r
-{\r
-       return SaveXmlDoc(m_savefilename.c_str());\r
-}\r
-\r
-bool XmlTagBuilder::SaveXmlDoc(const char* file)\r
-{\r
-  /* Writes the XML document\r
-\r
-     returns TRUE if the document was saved successfully or FALSE when saving failed\r
-  */\r
-  \r
-  xmlSaveNoEmptyTags = 1;\r
-\r
-  if(xmlSaveFile(file, doc) != -1)\r
-  {\r
-    return true;\r
-  }\r
-  return false;\r
-}\r
-\r
-bool XmlTagBuilder::AddShaderNode(const char* shader, TextureType textureType, NodeShaderType nodeShaderType)\r
-{\r
-  /* Adds a shader node\r
-\r
-     char* shader - the name of the shader or texture (without trailing .tga or something)\r
-\r
-     returns TRUE if the node was added successfully or FALSE when failed\r
-  */\r
-\r
-  xmlNodeSetPtr nodePtr = NULL;\r
-  xmlXPathObjectPtr xpathPtr = NULL;\r
-  \r
-  switch(textureType)\r
-  {\r
-    case STOCK:\r
-      xpathPtr = XpathEval("/root/stock");\r
-      break;\r
-    case CUSTOM:\r
-      xpathPtr = XpathEval("/root/custom");\r
-  };\r
-  \r
-  if(xpathPtr)\r
-    nodePtr = xpathPtr->nodesetval;\r
-  else\r
-    return false;\r
-\r
-  if(!xmlXPathNodeSetIsEmpty(nodePtr))\r
-  {\r
-    xmlNodePtr newnode, newtext;\r
-    xmlNodePtr nodeParent = nodePtr->nodeTab[0];\r
-\r
-    // create a new node and set the node attribute (shader path)\r
-    switch(nodeShaderType)\r
-    {\r
-      case SHADER:\r
-        newnode = xmlNewNode(NULL, (xmlChar*)"shader");\r
-        break;\r
-      case TEXTURE:\r
-        newnode = xmlNewNode(NULL, (xmlChar*)"texture");\r
-      };\r
-\r
-      newnode = xmlDocCopyNode(newnode, doc, 1);\r
-      xmlSetProp(newnode, (xmlChar*)"path", (xmlChar*)shader);\r
-      xmlNodeSetContent(newnode, (xmlChar*)"\n    ");\r
-\r
-      if(nodePtr->nodeTab[0]->children->next == NULL)   // there are no shaders yet\r
-      {\r
-        // add spaces\r
-        newtext = xmlNewText((xmlChar*)"  ");\r
-        xmlAddChild(nodeParent->children, newtext);\r
-\r
-        // add the new node\r
-        xmlAddNextSibling(nodeParent->children, newnode);\r
-\r
-        // append a new line\r
-        newtext = xmlNewText((xmlChar*)"\n  ");\r
-        xmlAddNextSibling(nodeParent->children->next, newtext);\r
-      } else {\r
-        // add the node\r
-        xmlAddNextSibling(nodeParent->children, newnode);\r
-\r
-        // append a new line and spaces\r
-        newtext = xmlNewText((xmlChar*)"\n    ");\r
-        xmlAddNextSibling(nodeParent->children->next, newtext);\r
-      }\r
-      \r
-      xmlXPathFreeObject(xpathPtr);\r
-      return true;\r
-  } else {\r
-    xmlXPathFreeObject(xpathPtr);\r
-    return false;\r
-  }\r
-}\r
-\r
-bool XmlTagBuilder::DeleteShaderNode(const char* shader)\r
-{\r
-  /* Deletes a shader node\r
-\r
-     char* shader - the name of the shader or texture (without trailing .tga or something)\r
-\r
-     returns TRUE if the node was deleted successfully or FALSE when failed\r
-  */\r
-\r
-  char buffer[256];\r
-  char* expression = GetTagsXpathExpression(buffer, shader, EMPTY);\r
-  xmlXPathObjectPtr xpathPtr = XpathEval(expression);\r
-  \r
-  xmlNodeSetPtr nodePtr;\r
-  if(xpathPtr)\r
-       nodePtr = xpathPtr->nodesetval;\r
-  else\r
-       return false;\r
-\r
-  if(!xmlXPathNodeSetIsEmpty(nodePtr))\r
-  {\r
-    xmlNodePtr ptrContent = nodePtr->nodeTab[0];\r
-    xmlNodePtr ptrWhitespace = nodePtr->nodeTab[0]->prev;\r
-\r
-    // delete the node\r
-    xmlUnlinkNode(ptrContent);\r
-    xmlFreeNode(ptrContent);\r
-\r
-    // delete leading whitespace node\r
-    xmlUnlinkNode(ptrWhitespace);\r
-    xmlFreeNode(ptrWhitespace);\r
-    xmlXPathFreeObject(xpathPtr);\r
-    return true;\r
-  }\r
-  xmlXPathFreeObject(xpathPtr);\r
-  return false;\r
-}\r
-\r
-bool XmlTagBuilder::CheckShaderTag(const char* shader)\r
-{\r
-  /* Checks whether there exists an entry for a shader/texture with at least one tag\r
-\r
-     char* shader  - the name of the shader or texture (without trailing .tga or something)\r
-\r
-     returns TRUE if the shader is already stored in the XML tag file and has at least one tag\r
-  */\r
-\r
-  // build the XPath expression to search for\r
-  char buffer[256];\r
-  strcpy(buffer, "/root/*/*[@path='");\r
-  strcat(buffer, shader);\r
-  strcat(buffer, "']");\r
-\r
-  char* expression = buffer;\r
-\r
-  xmlXPathObjectPtr xpathPtr = XpathEval(expression);\r
-  xmlNodeSetPtr nodePtr;\r
-  if(xpathPtr)\r
-    nodePtr = xpathPtr->nodesetval;\r
-  else\r
-       return false;\r
-\r
-  if(!xmlXPathNodeSetIsEmpty(nodePtr))\r
-  {\r
-    xmlXPathFreeObject(xpathPtr);\r
-    return true;\r
-  } else {\r
-    xmlXPathFreeObject(xpathPtr);\r
-    return false;\r
-  }\r
-}\r
-\r
-bool XmlTagBuilder::CheckShaderTag(const char* shader, const char* content)\r
-{\r
-  /* Checks whether a tag with content already exists\r
-\r
-     char* shader  - the name of the shader or texture (without trailing .tga or something)\r
-     char* content - the node content (a tag name)\r
-\r
-     returns TRUE if the tag with content already exists or FALSE if not\r
-  */\r
-\r
-  // build the XPath expression to search for\r
-  // example expression: "/stock/*[@path='textures/alpha/barb_wire'][child::tag='Alpha']";\r
-\r
-  char buffer[256];\r
-  strcpy(buffer, "/root/*/*[@path='");\r
-  strcat(buffer, shader);\r
-  strcat(buffer, "'][child::tag='");\r
-  strcat(buffer, content);\r
-  strcat(buffer, "']");\r
-\r
-  char* expression = buffer;\r
-\r
-  xmlXPathObjectPtr xpathPtr = XpathEval(expression);\r
-  xmlNodeSetPtr nodePtr;\r
-  if(xpathPtr)\r
-    nodePtr = xpathPtr->nodesetval;\r
-  else\r
-       return false;\r
-\r
-  if(!xmlXPathNodeSetIsEmpty(nodePtr))\r
-  {\r
-    xmlXPathFreeObject(xpathPtr);\r
-    return true;\r
-  } else {\r
-    xmlXPathFreeObject(xpathPtr);\r
-    return false;\r
-  }\r
-}\r
-\r
-bool XmlTagBuilder::AddShaderTag(const char* shader, const char* content, NodeTagType nodeTagType)\r
-{\r
-  /* Adds a tag node to an existing shader/texture node if there's no tag with the same content yet\r
-\r
-     char* shader  - the name of the shader or texture (without trailing .tga or something)\r
-     char* content - the node content (a tag name)\r
-\r
-     returns TRUE if the node was added successfully or FALSE when failed\r
-  */\r
-\r
-  // build the XPath expression\r
-  char buffer[256];\r
-  char* expression = GetTagsXpathExpression(buffer, shader, EMPTY);\r
-  \r
-  xmlXPathObjectPtr xpathPtr = XpathEval(expression);\r
-  xmlNodeSetPtr nodePtr;\r
-  if(xpathPtr)\r
-    nodePtr = xpathPtr->nodesetval;\r
-  else\r
-       return false;\r
-\r
-  if(!xmlXPathNodeSetIsEmpty(nodePtr)) // node was found\r
-  {\r
-    xmlNodePtr newnode = xmlNewNode(NULL, (xmlChar*)"tag");\r
-    xmlNodePtr nodeParent = nodePtr->nodeTab[0];\r
-    newnode = xmlDocCopyNode(newnode, doc, 1);\r
-    xmlNodeSetContent(newnode, (xmlChar*)content);\r
-\r
-    if(nodePtr->nodeTab[0]->children->next == NULL)    // shader node has NO children\r
-    {\r
-      // add spaces\r
-      xmlNodePtr newtext = xmlNewText((xmlChar*)"  ");\r
-      xmlAddChild(nodeParent->children, newtext);\r
-\r
-      // add new node\r
-      xmlAddNextSibling(nodeParent->children, newnode);\r
-\r
-      // append a new line + spaces\r
-      newtext = xmlNewText((xmlChar*)"\n    ");\r
-      xmlAddNextSibling(nodeParent->children->next, newtext);\r
-    } else { // shader node has children already - the new node will be the first sibling\r
-      xmlAddNextSibling(nodeParent->children, newnode);\r
-         xmlNodePtr newtext = xmlNewText((xmlChar*)"\n      ");\r
-      xmlAddNextSibling(nodeParent->children->next, newtext);\r
-    }\r
-       xmlXPathFreeObject(xpathPtr);\r
-    return true;\r
-  } else {\r
-    xmlXPathFreeObject(xpathPtr);\r
-    return false;\r
-  }\r
-}\r
-\r
-//int XmlTagBuilder::RenameShaderTag(const char* oldtag, const char* newtag)\r
-int XmlTagBuilder::RenameShaderTag(const char* oldtag, CopiedString newtag)\r
-{\r
-  /* Replaces tag node contents\r
-\r
-     char* oldtag - the <tag></tag> node content that sould be changed\r
-     char* newtag - the new <tag></tag> node content\r
-\r
-     returns the number of renamed shaders\r
-  */\r
-\r
-  int num = 0;\r
-\r
-  // build the XPath expression\r
-  char expression[256];\r
-  strcpy(expression, "/root/*/*[child::tag='");\r
-  strcat(expression, oldtag);\r
-  strcat(expression, "']/*");\r
-\r
-  xmlXPathObjectPtr result = xmlXPathEvalExpression((xmlChar*)expression, context);\r
-  if(!result)\r
-    return 0;\r
-  xmlNodeSetPtr nodePtr = result->nodesetval;\r
-\r
-  for(int i = 0; i < nodePtr->nodeNr; i++)\r
-  {\r
-    xmlNodePtr ptrContent = nodePtr->nodeTab[i];\r
-    char* content = (char*)xmlNodeGetContent(ptrContent);\r
-\r
-    if(strcmp(content, oldtag) == 0)   // found a node with old content?\r
-    {\r
-      xmlNodeSetContent(ptrContent, (xmlChar*)newtag.c_str());\r
-      num++;\r
-    }\r
-  }\r
-\r
-  SaveXmlDoc();\r
-  xmlXPathFreeObject(result);// CHANGED\r
-  return num;\r
-}\r
-\r
-bool XmlTagBuilder::DeleteShaderTag(const char* shader, const char* tag)\r
-{\r
-  /* Deletes a child node of a shader\r
-\r
-     char* shader  - the name of the shader or texture (without trailing .tga or something)\r
-     char* tag     - the tag being deleted\r
-\r
-     returns TRUE if the node was deleted successfully or FALSE when failed\r
-  */\r
-\r
-  char buffer[256];\r
-  char* expression = GetTagsXpathExpression(buffer, shader, TAG);\r
-  xmlXPathObjectPtr xpathPtr = XpathEval(expression);\r
-  xmlNodeSetPtr nodePtr;\r
-  if(xpathPtr)\r
-    nodePtr = xpathPtr->nodesetval;\r
-  else\r
-       return false;\r
-\r
-  if(!xmlXPathNodeSetIsEmpty(nodePtr))\r
-  {\r
-    for(int i = 0; i < nodePtr->nodeNr; i++)\r
-    {\r
-      xmlNodePtr ptrContent = nodePtr->nodeTab[i];\r
-      char* content = (char*)(xmlChar*)xmlNodeGetContent(ptrContent);\r
-\r
-      if(strcmp(content, tag) == 0)    // find the node\r
-      {\r
-        xmlNodePtr ptrWhitespace = nodePtr->nodeTab[i]->prev;\r
-        // delete the node\r
-        xmlUnlinkNode(ptrContent);\r
-        xmlFreeNode(ptrContent);\r
-\r
-        // delete leading whitespace node\r
-        xmlUnlinkNode(ptrWhitespace);\r
-        xmlFreeNode(ptrWhitespace);\r
-        xmlXPathFreeObject(xpathPtr);\r
-        return true;\r
-      }\r
-    }\r
-  }\r
-  xmlXPathFreeObject(xpathPtr);\r
-  return false;\r
-}\r
-\r
-bool XmlTagBuilder::DeleteTag(const char* tag)\r
-{\r
-  /* Deletes a tag from all shaders\r
-\r
-     char* tag - the tag being deleted from all shaders\r
-\r
-     returns TRUE if the tag was deleted successfully or FALSE when failed\r
-  */\r
-\r
-  char expression[256];\r
-  strcpy(expression, "/root/*/*[child::tag='");\r
-  strcat(expression, tag);\r
-  strcat(expression, "']");\r
-\r
-  std::set<CopiedString> dellist;\r
-  TagSearch(expression, dellist);\r
-  std::set<CopiedString>::iterator iter;\r
-\r
-  for(iter = dellist.begin(); iter != dellist.end(); iter++)\r
-  {\r
-    DeleteShaderTag(iter->c_str(), tag);\r
-  }\r
-  SaveXmlDoc();\r
-\r
-  return true;\r
-}\r
-\r
-void XmlTagBuilder::GetShaderTags(const char* shader, std::vector<CopiedString>& tags)\r
-{\r
-  /* Gets the tags from a shader\r
-\r
-     char* shader - the name of the shader\r
-\r
-     returns a vector containing the tags\r
-  */\r
-\r
-  char* expression;\r
-\r
-  if(shader == NULL)   // get all tags from all shaders\r
-  {\r
-    expression = "/root/*/*/tag";\r
-  } else {\r
-    char buffer[256];\r
-    expression = GetTagsXpathExpression(buffer, shader, TAG);\r
-  }\r
-\r
-  xmlXPathObjectPtr xpathPtr = XpathEval(expression);\r
-  xmlNodeSetPtr nodePtr;\r
-  if(xpathPtr)\r
-    nodePtr = xpathPtr->nodesetval;\r
-  else\r
-       return;\r
-\r
-  if(!xmlXPathNodeSetIsEmpty(nodePtr))\r
-  {\r
-    for(int i = 0; i < nodePtr->nodeNr; i++)\r
-    {\r
-      tags.push_back((CopiedString)(char*)xmlNodeGetContent(nodePtr->nodeTab[i]));\r
-    }\r
-  }\r
-  xmlXPathFreeObject(xpathPtr);\r
-}\r
-\r
-void XmlTagBuilder::GetUntagged(std::set<CopiedString>& shaders)\r
-{\r
-  /* Gets all textures and shaders listed in the xml file that don't have any tag\r
-\r
-     returns a set containing the shaders (with path)\r
-  */\r
-\r
-  char* expression = "/root/*/*[not(child::tag)]";\r
-\r
-  xmlXPathObjectPtr xpathPtr = XpathEval(expression);\r
-  xmlNodeSetPtr nodePtr;\r
-  if(xpathPtr)\r
-    nodePtr = xpathPtr->nodesetval;\r
-  else\r
-       return;\r
\r
-  if(!xmlXPathNodeSetIsEmpty(nodePtr))\r
-  {\r
-    xmlNodePtr ptr;\r
-\r
-    for(int i = 0; i < nodePtr->nodeNr; i++)\r
-    {\r
-      ptr = nodePtr->nodeTab[i];\r
-         shaders.insert((char*)xmlGetProp(ptr, (xmlChar*)"path"));\r
-    }\r
-  }\r
-  \r
-  xmlXPathFreeObject(xpathPtr);\r
-}\r
-\r
-void XmlTagBuilder::GetAllTags(std::set<CopiedString>& tags)\r
-{\r
-  /* Gets a list of all tags that are used (assigned to any shader)\r
-\r
-     returns a set containing all used tags\r
-  */\r
-\r
-  char* expression = "/root/*/*/tag";\r
-\r
-  xmlXPathObjectPtr xpathPtr = XpathEval(expression);\r
-  xmlNodeSetPtr nodePtr;\r
-  if(xpathPtr)\r
-    nodePtr = xpathPtr->nodesetval;\r
-  else\r
-       return;\r
\r
-  if(!xmlXPathNodeSetIsEmpty(nodePtr))\r
-  {\r
-    for(int i = 0; i < nodePtr->nodeNr; i++)\r
-    {\r
-      tags.insert((CopiedString)(char*)xmlNodeGetContent(nodePtr->nodeTab[i]));\r
-    }\r
-  }\r
-  \r
-  xmlXPathFreeObject(xpathPtr);\r
-}\r
-\r
-void XmlTagBuilder::TagSearch(const char* expression, std::set<CopiedString>& paths)\r
-{\r
-  /* Searches shaders by tags\r
-\r
-     char* expression - the XPath expression to search\r
-\r
-     returns a set containing the found shaders\r
-  */\r
-\r
-  xmlXPathObjectPtr xpathPtr = XpathEval(expression);\r
-  xmlNodeSetPtr nodePtr;\r
-  if(xpathPtr)\r
-    nodePtr = xpathPtr->nodesetval;\r
-  else\r
-       return;\r
-\r
-  if(!xmlXPathNodeSetIsEmpty(nodePtr))\r
-  {\r
-    xmlNodePtr ptr;\r
-    xmlChar* xmlattrib;\r
-    for(int i = 0; i < nodePtr->nodeNr; i++)\r
-    {\r
-      ptr = nodePtr->nodeTab[i];\r
-      xmlattrib = xmlGetProp(ptr, (xmlChar*)"path");\r
-         paths.insert((CopiedString)(char*)xmlattrib);\r
-    }\r
-  }\r
-  xmlXPathFreeObject(xpathPtr);\r
-}\r
-\r
+/*
+Copyright (C) 2006, Stefan Greven.
+All Rights Reserved.
+
+This file is part of GtkRadiant.
+
+GtkRadiant is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+GtkRadiant is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GtkRadiant; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#include "xmltextags.h"
+
+#include <string>
+
+#include "qerplugin.h"
+#include "stream/stringstream.h"
+
+XmlTagBuilder::XmlTagBuilder()
+{
+}
+
+XmlTagBuilder::~XmlTagBuilder()
+{
+  // clean up
+  xmlFreeDoc(doc);
+  xmlXPathFreeContext(context);
+}
+
+bool XmlTagBuilder::CreateXmlDocument()
+{
+  /* Creates an XML file
+
+     returns TRUE if the file was created successfully or FALSE when failed
+  */
+
+  xmlTextWriterPtr writer;
+
+  writer = xmlNewTextWriterDoc(&doc, 0);
+
+  // begin a new UTF-8 formatted xml document
+  xmlTextWriterStartDocument(writer, NULL, "UTF-8", NULL);
+
+  // create the root node with stock and custom elements
+  xmlTextWriterStartElement(writer, (xmlChar*)"root");
+  xmlTextWriterWriteString(writer, (xmlChar*)"\n  ");
+  xmlTextWriterStartElement(writer, (xmlChar*)"stock");
+  xmlTextWriterWriteString(writer, (xmlChar*)"\n  ");
+  xmlTextWriterEndElement(writer);
+  xmlTextWriterWriteString(writer, (xmlChar*)"\n  ");
+  xmlTextWriterStartElement(writer, (xmlChar*)"custom");
+  xmlTextWriterWriteString(writer, (xmlChar*)"\n  ");
+  xmlTextWriterEndElement(writer);
+  xmlTextWriterWriteString(writer, (xmlChar*)"\n");
+  xmlTextWriterEndElement(writer);
+
+  // end of the xml document
+  xmlTextWriterEndDocument(writer);
+  xmlFreeTextWriter(writer);
+
+  if(!doc)
+  {
+    return false;
+  } else {
+    context = xmlXPathNewContext(doc);
+       return true;
+  }
+}
+
+bool XmlTagBuilder::OpenXmlDoc(const char* file, const char* savefile)
+{
+  /* Reads a XML document from a file
+
+     returns TRUE if the document was read successfully or FALSE when failed
+  */
+
+  if(savefile)
+    m_savefilename = savefile;
+  else
+    m_savefilename = file;
+    
+  doc = xmlParseFile(file);    // TODO error checking!
+
+  if(!doc)
+  {
+    return false;
+  } else {
+    context = xmlXPathNewContext(doc);
+       return true;
+  }
+}
+
+bool XmlTagBuilder::SaveXmlDoc(void)
+{
+       return SaveXmlDoc(m_savefilename.c_str());
+}
+
+bool XmlTagBuilder::SaveXmlDoc(const char* file)
+{
+  /* Writes the XML document
+
+     returns TRUE if the document was saved successfully or FALSE when saving failed
+  */
+  
+  xmlSaveNoEmptyTags = 1;
+
+  if(xmlSaveFile(file, doc) != -1)
+  {
+    return true;
+  }
+  return false;
+}
+
+bool XmlTagBuilder::AddShaderNode(const char* shader, TextureType textureType, NodeShaderType nodeShaderType)
+{
+  /* Adds a shader node
+
+     char* shader - the name of the shader or texture (without trailing .tga or something)
+
+     returns TRUE if the node was added successfully or FALSE when failed
+  */
+
+  xmlNodeSetPtr nodePtr = NULL;
+  xmlXPathObjectPtr xpathPtr = NULL;
+  
+  switch(textureType)
+  {
+    case STOCK:
+      xpathPtr = XpathEval("/root/stock");
+      break;
+    case CUSTOM:
+      xpathPtr = XpathEval("/root/custom");
+  };
+  
+  if(xpathPtr)
+    nodePtr = xpathPtr->nodesetval;
+  else
+    return false;
+
+  if(!xmlXPathNodeSetIsEmpty(nodePtr))
+  {
+    xmlNodePtr newnode, newtext;
+    xmlNodePtr nodeParent = nodePtr->nodeTab[0];
+
+    // create a new node and set the node attribute (shader path)
+    switch(nodeShaderType)
+    {
+      case SHADER:
+        newnode = xmlNewNode(NULL, (xmlChar*)"shader");
+        break;
+      case TEXTURE:
+        newnode = xmlNewNode(NULL, (xmlChar*)"texture");
+      };
+
+      newnode = xmlDocCopyNode(newnode, doc, 1);
+      xmlSetProp(newnode, (xmlChar*)"path", (xmlChar*)shader);
+      xmlNodeSetContent(newnode, (xmlChar*)"\n    ");
+
+      if(nodePtr->nodeTab[0]->children->next == NULL)   // there are no shaders yet
+      {
+        // add spaces
+        newtext = xmlNewText((xmlChar*)"  ");
+        xmlAddChild(nodeParent->children, newtext);
+
+        // add the new node
+        xmlAddNextSibling(nodeParent->children, newnode);
+
+        // append a new line
+        newtext = xmlNewText((xmlChar*)"\n  ");
+        xmlAddNextSibling(nodeParent->children->next, newtext);
+      } else {
+        // add the node
+        xmlAddNextSibling(nodeParent->children, newnode);
+
+        // append a new line and spaces
+        newtext = xmlNewText((xmlChar*)"\n    ");
+        xmlAddNextSibling(nodeParent->children->next, newtext);
+      }
+      
+      xmlXPathFreeObject(xpathPtr);
+      return true;
+  } else {
+    xmlXPathFreeObject(xpathPtr);
+    return false;
+  }
+}
+
+bool XmlTagBuilder::DeleteShaderNode(const char* shader)
+{
+  /* Deletes a shader node
+
+     char* shader - the name of the shader or texture (without trailing .tga or something)
+
+     returns TRUE if the node was deleted successfully or FALSE when failed
+  */
+
+  char buffer[256];
+  char* expression = GetTagsXpathExpression(buffer, shader, EMPTY);
+  xmlXPathObjectPtr xpathPtr = XpathEval(expression);
+  
+  xmlNodeSetPtr nodePtr;
+  if(xpathPtr)
+       nodePtr = xpathPtr->nodesetval;
+  else
+       return false;
+
+  if(!xmlXPathNodeSetIsEmpty(nodePtr))
+  {
+    xmlNodePtr ptrContent = nodePtr->nodeTab[0];
+    xmlNodePtr ptrWhitespace = nodePtr->nodeTab[0]->prev;
+
+    // delete the node
+    xmlUnlinkNode(ptrContent);
+    xmlFreeNode(ptrContent);
+
+    // delete leading whitespace node
+    xmlUnlinkNode(ptrWhitespace);
+    xmlFreeNode(ptrWhitespace);
+    xmlXPathFreeObject(xpathPtr);
+    return true;
+  }
+  xmlXPathFreeObject(xpathPtr);
+  return false;
+}
+
+bool XmlTagBuilder::CheckShaderTag(const char* shader)
+{
+  /* Checks whether there exists an entry for a shader/texture with at least one tag
+
+     char* shader  - the name of the shader or texture (without trailing .tga or something)
+
+     returns TRUE if the shader is already stored in the XML tag file and has at least one tag
+  */
+
+  // build the XPath expression to search for
+  char buffer[256];
+  strcpy(buffer, "/root/*/*[@path='");
+  strcat(buffer, shader);
+  strcat(buffer, "']");
+
+  char* expression = buffer;
+
+  xmlXPathObjectPtr xpathPtr = XpathEval(expression);
+  xmlNodeSetPtr nodePtr;
+  if(xpathPtr)
+    nodePtr = xpathPtr->nodesetval;
+  else
+       return false;
+
+  if(!xmlXPathNodeSetIsEmpty(nodePtr))
+  {
+    xmlXPathFreeObject(xpathPtr);
+    return true;
+  } else {
+    xmlXPathFreeObject(xpathPtr);
+    return false;
+  }
+}
+
+bool XmlTagBuilder::CheckShaderTag(const char* shader, const char* content)
+{
+  /* Checks whether a tag with content already exists
+
+     char* shader  - the name of the shader or texture (without trailing .tga or something)
+     char* content - the node content (a tag name)
+
+     returns TRUE if the tag with content already exists or FALSE if not
+  */
+
+  // build the XPath expression to search for
+  // example expression: "/stock/*[@path='textures/alpha/barb_wire'][child::tag='Alpha']";
+
+  char buffer[256];
+  strcpy(buffer, "/root/*/*[@path='");
+  strcat(buffer, shader);
+  strcat(buffer, "'][child::tag='");
+  strcat(buffer, content);
+  strcat(buffer, "']");
+
+  char* expression = buffer;
+
+  xmlXPathObjectPtr xpathPtr = XpathEval(expression);
+  xmlNodeSetPtr nodePtr;
+  if(xpathPtr)
+    nodePtr = xpathPtr->nodesetval;
+  else
+       return false;
+
+  if(!xmlXPathNodeSetIsEmpty(nodePtr))
+  {
+    xmlXPathFreeObject(xpathPtr);
+    return true;
+  } else {
+    xmlXPathFreeObject(xpathPtr);
+    return false;
+  }
+}
+
+bool XmlTagBuilder::AddShaderTag(const char* shader, const char* content, NodeTagType nodeTagType)
+{
+  /* Adds a tag node to an existing shader/texture node if there's no tag with the same content yet
+
+     char* shader  - the name of the shader or texture (without trailing .tga or something)
+     char* content - the node content (a tag name)
+
+     returns TRUE if the node was added successfully or FALSE when failed
+  */
+
+  // build the XPath expression
+  char buffer[256];
+  char* expression = GetTagsXpathExpression(buffer, shader, EMPTY);
+  
+  xmlXPathObjectPtr xpathPtr = XpathEval(expression);
+  xmlNodeSetPtr nodePtr;
+  if(xpathPtr)
+    nodePtr = xpathPtr->nodesetval;
+  else
+       return false;
+
+  if(!xmlXPathNodeSetIsEmpty(nodePtr)) // node was found
+  {
+    xmlNodePtr newnode = xmlNewNode(NULL, (xmlChar*)"tag");
+    xmlNodePtr nodeParent = nodePtr->nodeTab[0];
+    newnode = xmlDocCopyNode(newnode, doc, 1);
+    xmlNodeSetContent(newnode, (xmlChar*)content);
+
+    if(nodePtr->nodeTab[0]->children->next == NULL)    // shader node has NO children
+    {
+      // add spaces
+      xmlNodePtr newtext = xmlNewText((xmlChar*)"  ");
+      xmlAddChild(nodeParent->children, newtext);
+
+      // add new node
+      xmlAddNextSibling(nodeParent->children, newnode);
+
+      // append a new line + spaces
+      newtext = xmlNewText((xmlChar*)"\n    ");
+      xmlAddNextSibling(nodeParent->children->next, newtext);
+    } else { // shader node has children already - the new node will be the first sibling
+      xmlAddNextSibling(nodeParent->children, newnode);
+         xmlNodePtr newtext = xmlNewText((xmlChar*)"\n      ");
+      xmlAddNextSibling(nodeParent->children->next, newtext);
+    }
+       xmlXPathFreeObject(xpathPtr);
+    return true;
+  } else {
+    xmlXPathFreeObject(xpathPtr);
+    return false;
+  }
+}
+
+//int XmlTagBuilder::RenameShaderTag(const char* oldtag, const char* newtag)
+int XmlTagBuilder::RenameShaderTag(const char* oldtag, CopiedString newtag)
+{
+  /* Replaces tag node contents
+
+     char* oldtag - the <tag></tag> node content that sould be changed
+     char* newtag - the new <tag></tag> node content
+
+     returns the number of renamed shaders
+  */
+
+  int num = 0;
+
+  // build the XPath expression
+  char expression[256];
+  strcpy(expression, "/root/*/*[child::tag='");
+  strcat(expression, oldtag);
+  strcat(expression, "']/*");
+
+  xmlXPathObjectPtr result = xmlXPathEvalExpression((xmlChar*)expression, context);
+  if(!result)
+    return 0;
+  xmlNodeSetPtr nodePtr = result->nodesetval;
+
+  for(int i = 0; i < nodePtr->nodeNr; i++)
+  {
+    xmlNodePtr ptrContent = nodePtr->nodeTab[i];
+    char* content = (char*)xmlNodeGetContent(ptrContent);
+
+    if(strcmp(content, oldtag) == 0)   // found a node with old content?
+    {
+      xmlNodeSetContent(ptrContent, (xmlChar*)newtag.c_str());
+      num++;
+    }
+  }
+
+  SaveXmlDoc();
+  xmlXPathFreeObject(result);// CHANGED
+  return num;
+}
+
+bool XmlTagBuilder::DeleteShaderTag(const char* shader, const char* tag)
+{
+  /* Deletes a child node of a shader
+
+     char* shader  - the name of the shader or texture (without trailing .tga or something)
+     char* tag     - the tag being deleted
+
+     returns TRUE if the node was deleted successfully or FALSE when failed
+  */
+
+  char buffer[256];
+  char* expression = GetTagsXpathExpression(buffer, shader, TAG);
+  xmlXPathObjectPtr xpathPtr = XpathEval(expression);
+  xmlNodeSetPtr nodePtr;
+  if(xpathPtr)
+    nodePtr = xpathPtr->nodesetval;
+  else
+       return false;
+
+  if(!xmlXPathNodeSetIsEmpty(nodePtr))
+  {
+    for(int i = 0; i < nodePtr->nodeNr; i++)
+    {
+      xmlNodePtr ptrContent = nodePtr->nodeTab[i];
+      char* content = (char*)(xmlChar*)xmlNodeGetContent(ptrContent);
+
+      if(strcmp(content, tag) == 0)    // find the node
+      {
+        xmlNodePtr ptrWhitespace = nodePtr->nodeTab[i]->prev;
+        // delete the node
+        xmlUnlinkNode(ptrContent);
+        xmlFreeNode(ptrContent);
+
+        // delete leading whitespace node
+        xmlUnlinkNode(ptrWhitespace);
+        xmlFreeNode(ptrWhitespace);
+        xmlXPathFreeObject(xpathPtr);
+        return true;
+      }
+    }
+  }
+  xmlXPathFreeObject(xpathPtr);
+  return false;
+}
+
+bool XmlTagBuilder::DeleteTag(const char* tag)
+{
+  /* Deletes a tag from all shaders
+
+     char* tag - the tag being deleted from all shaders
+
+     returns TRUE if the tag was deleted successfully or FALSE when failed
+  */
+
+  char expression[256];
+  strcpy(expression, "/root/*/*[child::tag='");
+  strcat(expression, tag);
+  strcat(expression, "']");
+
+  std::set<CopiedString> dellist;
+  TagSearch(expression, dellist);
+  std::set<CopiedString>::iterator iter;
+
+  for(iter = dellist.begin(); iter != dellist.end(); iter++)
+  {
+    DeleteShaderTag(iter->c_str(), tag);
+  }
+  SaveXmlDoc();
+
+  return true;
+}
+
+void XmlTagBuilder::GetShaderTags(const char* shader, std::vector<CopiedString>& tags)
+{
+  /* Gets the tags from a shader
+
+     char* shader - the name of the shader
+
+     returns a vector containing the tags
+  */
+
+  char* expression;
+
+  if(shader == NULL)   // get all tags from all shaders
+  {
+    expression = "/root/*/*/tag";
+  } else {
+    char buffer[256];
+    expression = GetTagsXpathExpression(buffer, shader, TAG);
+  }
+
+  xmlXPathObjectPtr xpathPtr = XpathEval(expression);
+  xmlNodeSetPtr nodePtr;
+  if(xpathPtr)
+    nodePtr = xpathPtr->nodesetval;
+  else
+       return;
+
+  if(!xmlXPathNodeSetIsEmpty(nodePtr))
+  {
+    for(int i = 0; i < nodePtr->nodeNr; i++)
+    {
+      tags.push_back((CopiedString)(char*)xmlNodeGetContent(nodePtr->nodeTab[i]));
+    }
+  }
+  xmlXPathFreeObject(xpathPtr);
+}
+
+void XmlTagBuilder::GetUntagged(std::set<CopiedString>& shaders)
+{
+  /* Gets all textures and shaders listed in the xml file that don't have any tag
+
+     returns a set containing the shaders (with path)
+  */
+
+  char* expression = "/root/*/*[not(child::tag)]";
+
+  xmlXPathObjectPtr xpathPtr = XpathEval(expression);
+  xmlNodeSetPtr nodePtr;
+  if(xpathPtr)
+    nodePtr = xpathPtr->nodesetval;
+  else
+       return;
+  if(!xmlXPathNodeSetIsEmpty(nodePtr))
+  {
+    xmlNodePtr ptr;
+
+    for(int i = 0; i < nodePtr->nodeNr; i++)
+    {
+      ptr = nodePtr->nodeTab[i];
+         shaders.insert((char*)xmlGetProp(ptr, (xmlChar*)"path"));
+    }
+  }
+  
+  xmlXPathFreeObject(xpathPtr);
+}
+
+void XmlTagBuilder::GetAllTags(std::set<CopiedString>& tags)
+{
+  /* Gets a list of all tags that are used (assigned to any shader)
+
+     returns a set containing all used tags
+  */
+
+  char* expression = "/root/*/*/tag";
+
+  xmlXPathObjectPtr xpathPtr = XpathEval(expression);
+  xmlNodeSetPtr nodePtr;
+  if(xpathPtr)
+    nodePtr = xpathPtr->nodesetval;
+  else
+       return;
+  if(!xmlXPathNodeSetIsEmpty(nodePtr))
+  {
+    for(int i = 0; i < nodePtr->nodeNr; i++)
+    {
+      tags.insert((CopiedString)(char*)xmlNodeGetContent(nodePtr->nodeTab[i]));
+    }
+  }
+  
+  xmlXPathFreeObject(xpathPtr);
+}
+
+void XmlTagBuilder::TagSearch(const char* expression, std::set<CopiedString>& paths)
+{
+  /* Searches shaders by tags
+
+     char* expression - the XPath expression to search
+
+     returns a set containing the found shaders
+  */
+
+  xmlXPathObjectPtr xpathPtr = XpathEval(expression);
+  xmlNodeSetPtr nodePtr;
+  if(xpathPtr)
+    nodePtr = xpathPtr->nodesetval;
+  else
+       return;
+
+  if(!xmlXPathNodeSetIsEmpty(nodePtr))
+  {
+    xmlNodePtr ptr;
+    xmlChar* xmlattrib;
+    for(int i = 0; i < nodePtr->nodeNr; i++)
+    {
+      ptr = nodePtr->nodeTab[i];
+      xmlattrib = xmlGetProp(ptr, (xmlChar*)"path");
+         paths.insert((CopiedString)(char*)xmlattrib);
+    }
+  }
+  xmlXPathFreeObject(xpathPtr);
+}
+