]> de.git.xonotic.org Git - xonotic/netradiant.git/blobdiff - libs/xml/xmltextags.cpp
Merge branch 'NateEag-master-patch-12920' into 'master'
[xonotic/netradiant.git] / libs / xml / xmltextags.cpp
index 4ab2c6c3c0171a489b1fe3aa9dbb4660c1879bf1..3d6665f21e7846bb5d944ecea4f17155d403c2b2 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 "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 const *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 const *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 const *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 );
+}