00f57a057ac2337eec22fa81e6e8bd247793fbf3
[xonotic/netradiant.git] / libs / xml / xmltextags.cpp
1 /*
2    Copyright (C) 2006, Stefan Greven.
3    All Rights Reserved.
4
5    This file is part of GtkRadiant.
6
7    GtkRadiant is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    GtkRadiant is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with GtkRadiant; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21
22 #include "xmltextags.h"
23
24 #include <string>
25
26 #include "qerplugin.h"
27 #include "stream/stringstream.h"
28
29 XmlTagBuilder::XmlTagBuilder(){
30 }
31
32 XmlTagBuilder::~XmlTagBuilder(){
33         // clean up
34         xmlFreeDoc( doc );
35         xmlXPathFreeContext( context );
36 }
37
38 bool XmlTagBuilder::CreateXmlDocument(){
39         /* Creates an XML file
40
41            returns TRUE if the file was created successfully or FALSE when failed
42          */
43
44         xmlTextWriterPtr writer;
45
46         writer = xmlNewTextWriterDoc( &doc, 0 );
47
48         // begin a new UTF-8 formatted xml document
49         xmlTextWriterStartDocument( writer, NULL, "UTF-8", NULL );
50
51         // create the root node with stock and custom elements
52         xmlTextWriterStartElement( writer, (xmlChar*)"root" );
53         xmlTextWriterWriteString( writer, (xmlChar*)"\n  " );
54         xmlTextWriterStartElement( writer, (xmlChar*)"stock" );
55         xmlTextWriterWriteString( writer, (xmlChar*)"\n  " );
56         xmlTextWriterEndElement( writer );
57         xmlTextWriterWriteString( writer, (xmlChar*)"\n  " );
58         xmlTextWriterStartElement( writer, (xmlChar*)"custom" );
59         xmlTextWriterWriteString( writer, (xmlChar*)"\n  " );
60         xmlTextWriterEndElement( writer );
61         xmlTextWriterWriteString( writer, (xmlChar*)"\n" );
62         xmlTextWriterEndElement( writer );
63
64         // end of the xml document
65         xmlTextWriterEndDocument( writer );
66         xmlFreeTextWriter( writer );
67
68         if ( !doc ) {
69                 return false;
70         }
71         else {
72                 context = xmlXPathNewContext( doc );
73                 return true;
74         }
75 }
76
77 bool XmlTagBuilder::OpenXmlDoc( const char* file, const char* savefile ){
78         /* Reads a XML document from a file
79
80            returns TRUE if the document was read successfully or FALSE when failed
81          */
82
83         if ( savefile ) {
84                 m_savefilename = savefile;
85         }
86         else{
87                 m_savefilename = file;
88         }
89
90         doc = xmlParseFile( file ); // TODO error checking!
91
92         if ( !doc ) {
93                 return false;
94         }
95         else {
96                 context = xmlXPathNewContext( doc );
97                 return true;
98         }
99 }
100
101 bool XmlTagBuilder::SaveXmlDoc( void ){
102         return SaveXmlDoc( m_savefilename.c_str() );
103 }
104
105 bool XmlTagBuilder::SaveXmlDoc( const char* file ){
106         /* Writes the XML document
107
108            returns TRUE if the document was saved successfully or FALSE when saving failed
109          */
110
111         xmlSaveNoEmptyTags = 1;
112
113         if ( xmlSaveFile( file, doc ) != -1 ) {
114                 return true;
115         }
116         return false;
117 }
118
119 bool XmlTagBuilder::AddShaderNode( const char* shader, TextureType textureType, NodeShaderType nodeShaderType ){
120         /* Adds a shader node
121
122            char* shader - the name of the shader or texture (without trailing .tga or something)
123
124            returns TRUE if the node was added successfully or FALSE when failed
125          */
126
127         xmlNodeSetPtr nodePtr = NULL;
128         xmlXPathObjectPtr xpathPtr = NULL;
129
130         switch ( textureType )
131         {
132         case STOCK:
133                 xpathPtr = XpathEval( "/root/stock" );
134                 break;
135         case CUSTOM:
136                 xpathPtr = XpathEval( "/root/custom" );
137         };
138
139         if ( xpathPtr ) {
140                 nodePtr = xpathPtr->nodesetval;
141         }
142         else{
143                 return false;
144         }
145
146         if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) {
147                 xmlNodePtr newnode, newtext;
148                 xmlNodePtr nodeParent = nodePtr->nodeTab[0];
149
150                 // create a new node and set the node attribute (shader path)
151                 switch ( nodeShaderType )
152                 {
153                 case SHADER:
154                         newnode = xmlNewNode( NULL, (xmlChar*)"shader" );
155                         break;
156                 case TEXTURE:
157                         newnode = xmlNewNode( NULL, (xmlChar*)"texture" );
158                 };
159
160                 newnode = xmlDocCopyNode( newnode, doc, 1 );
161                 xmlSetProp( newnode, (xmlChar*)"path", (xmlChar*)shader );
162                 xmlNodeSetContent( newnode, (xmlChar*)"\n    " );
163
164                 if ( nodePtr->nodeTab[0]->children->next == NULL ) { // there are no shaders yet
165                         // add spaces
166                         newtext = xmlNewText( (xmlChar*)"  " );
167                         xmlAddChild( nodeParent->children, newtext );
168
169                         // add the new node
170                         xmlAddNextSibling( nodeParent->children, newnode );
171
172                         // append a new line
173                         newtext = xmlNewText( (xmlChar*)"\n  " );
174                         xmlAddNextSibling( nodeParent->children->next, newtext );
175                 }
176                 else {
177                         // add the node
178                         xmlAddNextSibling( nodeParent->children, newnode );
179
180                         // append a new line and spaces
181                         newtext = xmlNewText( (xmlChar*)"\n    " );
182                         xmlAddNextSibling( nodeParent->children->next, newtext );
183                 }
184
185                 xmlXPathFreeObject( xpathPtr );
186                 return true;
187         }
188         else {
189                 xmlXPathFreeObject( xpathPtr );
190                 return false;
191         }
192 }
193
194 bool XmlTagBuilder::DeleteShaderNode( const char* shader ){
195         /* Deletes a shader node
196
197            char* shader - the name of the shader or texture (without trailing .tga or something)
198
199            returns TRUE if the node was deleted successfully or FALSE when failed
200          */
201
202         char buffer[256];
203         char* expression = GetTagsXpathExpression( buffer, shader, EMPTY );
204         xmlXPathObjectPtr xpathPtr = XpathEval( expression );
205
206         xmlNodeSetPtr nodePtr;
207         if ( xpathPtr ) {
208                 nodePtr = xpathPtr->nodesetval;
209         }
210         else{
211                 return false;
212         }
213
214         if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) {
215                 xmlNodePtr ptrContent = nodePtr->nodeTab[0];
216                 xmlNodePtr ptrWhitespace = nodePtr->nodeTab[0]->prev;
217
218                 // delete the node
219                 xmlUnlinkNode( ptrContent );
220                 xmlFreeNode( ptrContent );
221
222                 // delete leading whitespace node
223                 xmlUnlinkNode( ptrWhitespace );
224                 xmlFreeNode( ptrWhitespace );
225                 xmlXPathFreeObject( xpathPtr );
226                 return true;
227         }
228         xmlXPathFreeObject( xpathPtr );
229         return false;
230 }
231
232 bool XmlTagBuilder::CheckShaderTag( const char* shader ){
233         /* Checks whether there exists an entry for a shader/texture with at least one tag
234
235            char* shader  - the name of the shader or texture (without trailing .tga or something)
236
237            returns TRUE if the shader is already stored in the XML tag file and has at least one tag
238          */
239
240         // build the XPath expression to search for
241         char buffer[256];
242         strcpy( buffer, "/root/*/*[@path='" );
243         strcat( buffer, shader );
244         strcat( buffer, "']" );
245
246         char* expression = buffer;
247
248         xmlXPathObjectPtr xpathPtr = XpathEval( expression );
249         xmlNodeSetPtr nodePtr;
250         if ( xpathPtr ) {
251                 nodePtr = xpathPtr->nodesetval;
252         }
253         else{
254                 return false;
255         }
256
257         if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) {
258                 xmlXPathFreeObject( xpathPtr );
259                 return true;
260         }
261         else {
262                 xmlXPathFreeObject( xpathPtr );
263                 return false;
264         }
265 }
266
267 bool XmlTagBuilder::CheckShaderTag( const char* shader, const char* content ){
268         /* Checks whether a tag with content already exists
269
270            char* shader  - the name of the shader or texture (without trailing .tga or something)
271            char* content - the node content (a tag name)
272
273            returns TRUE if the tag with content already exists or FALSE if not
274          */
275
276         // build the XPath expression to search for
277         // example expression: "/stock/*[@path='textures/alpha/barb_wire'][child::tag='Alpha']";
278
279         char buffer[256];
280         strcpy( buffer, "/root/*/*[@path='" );
281         strcat( buffer, shader );
282         strcat( buffer, "'][child::tag='" );
283         strcat( buffer, content );
284         strcat( buffer, "']" );
285
286         char* expression = buffer;
287
288         xmlXPathObjectPtr xpathPtr = XpathEval( expression );
289         xmlNodeSetPtr nodePtr;
290         if ( xpathPtr ) {
291                 nodePtr = xpathPtr->nodesetval;
292         }
293         else{
294                 return false;
295         }
296
297         if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) {
298                 xmlXPathFreeObject( xpathPtr );
299                 return true;
300         }
301         else {
302                 xmlXPathFreeObject( xpathPtr );
303                 return false;
304         }
305 }
306
307 bool XmlTagBuilder::AddShaderTag( const char* shader, const char* content, NodeTagType nodeTagType ){
308         /* Adds a tag node to an existing shader/texture node if there's no tag with the same content yet
309
310            char* shader  - the name of the shader or texture (without trailing .tga or something)
311            char* content - the node content (a tag name)
312
313            returns TRUE if the node was added successfully or FALSE when failed
314          */
315
316         // build the XPath expression
317         char buffer[256];
318         char* expression = GetTagsXpathExpression( buffer, shader, EMPTY );
319
320         xmlXPathObjectPtr xpathPtr = XpathEval( expression );
321         xmlNodeSetPtr nodePtr;
322         if ( xpathPtr ) {
323                 nodePtr = xpathPtr->nodesetval;
324         }
325         else{
326                 return false;
327         }
328
329         if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) { // node was found
330                 xmlNodePtr newnode = xmlNewNode( NULL, (xmlChar*)"tag" );
331                 xmlNodePtr nodeParent = nodePtr->nodeTab[0];
332                 newnode = xmlDocCopyNode( newnode, doc, 1 );
333                 xmlNodeSetContent( newnode, (xmlChar*)content );
334
335                 if ( nodePtr->nodeTab[0]->children->next == NULL ) { // shader node has NO children
336                         // add spaces
337                         xmlNodePtr newtext = xmlNewText( (xmlChar*)"  " );
338                         xmlAddChild( nodeParent->children, newtext );
339
340                         // add new node
341                         xmlAddNextSibling( nodeParent->children, newnode );
342
343                         // append a new line + spaces
344                         newtext = xmlNewText( (xmlChar*)"\n    " );
345                         xmlAddNextSibling( nodeParent->children->next, newtext );
346                 }
347                 else { // shader node has children already - the new node will be the first sibling
348                         xmlAddNextSibling( nodeParent->children, newnode );
349                         xmlNodePtr newtext = xmlNewText( (xmlChar*)"\n      " );
350                         xmlAddNextSibling( nodeParent->children->next, newtext );
351                 }
352                 xmlXPathFreeObject( xpathPtr );
353                 return true;
354         }
355         else {
356                 xmlXPathFreeObject( xpathPtr );
357                 return false;
358         }
359 }
360
361 //int XmlTagBuilder::RenameShaderTag(const char* oldtag, const char* newtag)
362 int XmlTagBuilder::RenameShaderTag( const char* oldtag, CopiedString newtag ){
363         /* Replaces tag node contents
364
365            char* oldtag - the <tag></tag> node content that sould be changed
366            char* newtag - the new <tag></tag> node content
367
368            returns the number of renamed shaders
369          */
370
371         int num = 0;
372
373         // build the XPath expression
374         char expression[256];
375         strcpy( expression, "/root/*/*[child::tag='" );
376         strcat( expression, oldtag );
377         strcat( expression, "']/*" );
378
379         xmlXPathObjectPtr result = xmlXPathEvalExpression( (xmlChar*)expression, context );
380         if ( !result ) {
381                 return 0;
382         }
383         xmlNodeSetPtr nodePtr = result->nodesetval;
384
385         for ( int i = 0; i < nodePtr->nodeNr; i++ )
386         {
387                 xmlNodePtr ptrContent = nodePtr->nodeTab[i];
388                 char* content = (char*)xmlNodeGetContent( ptrContent );
389
390                 if ( strcmp( content, oldtag ) == 0 ) { // found a node with old content?
391                         xmlNodeSetContent( ptrContent, (xmlChar*)newtag.c_str() );
392                         num++;
393                 }
394         }
395
396         SaveXmlDoc();
397         xmlXPathFreeObject( result ); // CHANGED
398         return num;
399 }
400
401 bool XmlTagBuilder::DeleteShaderTag( const char* shader, const char* tag ){
402         /* Deletes a child node of a shader
403
404            char* shader  - the name of the shader or texture (without trailing .tga or something)
405            char* tag     - the tag being deleted
406
407            returns TRUE if the node was deleted successfully or FALSE when failed
408          */
409
410         char buffer[256];
411         char* expression = GetTagsXpathExpression( buffer, shader, TAG );
412         xmlXPathObjectPtr xpathPtr = XpathEval( expression );
413         xmlNodeSetPtr nodePtr;
414         if ( xpathPtr ) {
415                 nodePtr = xpathPtr->nodesetval;
416         }
417         else{
418                 return false;
419         }
420
421         if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) {
422                 for ( int i = 0; i < nodePtr->nodeNr; i++ )
423                 {
424                         xmlNodePtr ptrContent = nodePtr->nodeTab[i];
425                         char* content = (char*)(xmlChar*)xmlNodeGetContent( ptrContent );
426
427                         if ( strcmp( content, tag ) == 0 ) { // find the node
428                                 xmlNodePtr ptrWhitespace = nodePtr->nodeTab[i]->prev;
429                                 // delete the node
430                                 xmlUnlinkNode( ptrContent );
431                                 xmlFreeNode( ptrContent );
432
433                                 // delete leading whitespace node
434                                 xmlUnlinkNode( ptrWhitespace );
435                                 xmlFreeNode( ptrWhitespace );
436                                 xmlXPathFreeObject( xpathPtr );
437                                 return true;
438                         }
439                 }
440         }
441         xmlXPathFreeObject( xpathPtr );
442         return false;
443 }
444
445 bool XmlTagBuilder::DeleteTag( const char* tag ){
446         /* Deletes a tag from all shaders
447
448            char* tag - the tag being deleted from all shaders
449
450            returns TRUE if the tag was deleted successfully or FALSE when failed
451          */
452
453         char expression[256];
454         strcpy( expression, "/root/*/*[child::tag='" );
455         strcat( expression, tag );
456         strcat( expression, "']" );
457
458         std::set<CopiedString> dellist;
459         TagSearch( expression, dellist );
460         std::set<CopiedString>::iterator iter;
461
462         for ( iter = dellist.begin(); iter != dellist.end(); iter++ )
463         {
464                 DeleteShaderTag( iter->c_str(), tag );
465         }
466         SaveXmlDoc();
467
468         return true;
469 }
470
471 void XmlTagBuilder::GetShaderTags( const char* shader, std::vector<CopiedString>& tags ){
472         /* Gets the tags from a shader
473
474            char* shader - the name of the shader
475
476            returns a vector containing the tags
477          */
478
479         char const *expression;
480
481         if ( shader == NULL ) { // get all tags from all shaders
482                 expression = "/root/*/*/tag";
483         }
484         else {
485                 char buffer[256];
486                 expression = GetTagsXpathExpression( buffer, shader, TAG );
487         }
488
489         xmlXPathObjectPtr xpathPtr = XpathEval( expression );
490         xmlNodeSetPtr nodePtr;
491         if ( xpathPtr ) {
492                 nodePtr = xpathPtr->nodesetval;
493         }
494         else{
495                 return;
496         }
497
498         if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) {
499                 for ( int i = 0; i < nodePtr->nodeNr; i++ )
500                 {
501                         tags.push_back( (CopiedString)(char*)xmlNodeGetContent( nodePtr->nodeTab[i] ) );
502                 }
503         }
504         xmlXPathFreeObject( xpathPtr );
505 }
506
507 void XmlTagBuilder::GetUntagged( std::set<CopiedString>& shaders ){
508         /* Gets all textures and shaders listed in the xml file that don't have any tag
509
510            returns a set containing the shaders (with path)
511          */
512
513         char const *expression = "/root/*/*[not(child::tag)]";
514
515         xmlXPathObjectPtr xpathPtr = XpathEval( expression );
516         xmlNodeSetPtr nodePtr;
517         if ( xpathPtr ) {
518                 nodePtr = xpathPtr->nodesetval;
519         }
520         else{
521                 return;
522         }
523
524         if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) {
525                 xmlNodePtr ptr;
526
527                 for ( int i = 0; i < nodePtr->nodeNr; i++ )
528                 {
529                         ptr = nodePtr->nodeTab[i];
530                         shaders.insert( (char*)xmlGetProp( ptr, (xmlChar*)"path" ) );
531                 }
532         }
533
534         xmlXPathFreeObject( xpathPtr );
535 }
536
537 void XmlTagBuilder::GetAllTags( std::set<CopiedString>& tags ){
538         /* Gets a list of all tags that are used (assigned to any shader)
539
540            returns a set containing all used tags
541          */
542
543         char const *expression = "/root/*/*/tag";
544
545         xmlXPathObjectPtr xpathPtr = XpathEval( expression );
546         xmlNodeSetPtr nodePtr;
547         if ( xpathPtr ) {
548                 nodePtr = xpathPtr->nodesetval;
549         }
550         else{
551                 return;
552         }
553
554         if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) {
555                 for ( int i = 0; i < nodePtr->nodeNr; i++ )
556                 {
557                         tags.insert( (CopiedString)(char*)xmlNodeGetContent( nodePtr->nodeTab[i] ) );
558                 }
559         }
560
561         xmlXPathFreeObject( xpathPtr );
562 }
563
564 void XmlTagBuilder::TagSearch( const char* expression, std::set<CopiedString>& paths ){
565         /* Searches shaders by tags
566
567            char* expression - the XPath expression to search
568
569            returns a set containing the found shaders
570          */
571
572         xmlXPathObjectPtr xpathPtr = XpathEval( expression );
573         xmlNodeSetPtr nodePtr;
574         if ( xpathPtr ) {
575                 nodePtr = xpathPtr->nodesetval;
576         }
577         else{
578                 return;
579         }
580
581         if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) {
582                 xmlNodePtr ptr;
583                 xmlChar* xmlattrib;
584                 for ( int i = 0; i < nodePtr->nodeNr; i++ )
585                 {
586                         ptr = nodePtr->nodeTab[i];
587                         xmlattrib = xmlGetProp( ptr, (xmlChar*)"path" );
588                         paths.insert( (CopiedString)(char*)xmlattrib );
589                 }
590         }
591         xmlXPathFreeObject( xpathPtr );
592 }