]> git.sesse.net Git - vlc/blob - modules/gui/skins2/parser/skin_parser.cpp
FSF address change.
[vlc] / modules / gui / skins2 / parser / skin_parser.cpp
1 /*****************************************************************************
2  * skin_parser.cpp
3  *****************************************************************************
4  * Copyright (C) 2004 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Cyril Deguet     <asmax@via.ecp.fr>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #include "skin_parser.hpp"
25 #include "../src/os_factory.hpp"
26 #include <math.h>
27
28 SkinParser::SkinParser( intf_thread_t *pIntf, const string &rFileName,
29                         const string &rPath, bool useDTD, BuilderData *pData ):
30     XMLParser( pIntf, rFileName, useDTD ), m_path( rPath), m_pData(pData),
31     m_ownData(pData == NULL), m_xOffset( 0 ), m_yOffset( 0 )
32 {
33     // Make sure the data is allocated
34     if( m_pData == NULL )
35     {
36         m_pData = new BuilderData();
37     }
38 }
39
40
41 SkinParser::~SkinParser()
42 {
43     if( m_ownData )
44     {
45         delete m_pData;
46     }
47 }
48
49
50 void SkinParser::handleBeginElement( const string &rName, AttrList_t &attr )
51 {
52 #define CheckDefault( a, b ) \
53     if( attr.find(a) == attr.end() ) attr[strdup(a)] = strdup(b);
54 #define RequireDefault( a ) \
55     if( attr.find(a) == attr.end() ) \
56     { \
57         msg_Err( getIntf(), "Bad theme (element: %s, missing attribute: %s)", \
58                  rName.c_str(), a ); \
59         m_errors = true; return; \
60     }
61
62     if( rName == "Include" )
63     {
64         RequireDefault( "file" );
65
66         OSFactory *pFactory = OSFactory::instance( getIntf() );
67         string fullPath = m_path + pFactory->getDirSeparator() + attr["file"];
68         msg_Dbg( getIntf(), "Opening included XML file: %s", fullPath.c_str() );
69         // FIXME: We do not use the DTD to validate the included XML file,
70         // as the parser seems to dislike it otherwise...
71         SkinParser subParser( getIntf(), fullPath.c_str(), false, m_pData );
72         subParser.parse();
73     }
74
75     else if( rName == "Anchor" )
76     {
77         RequireDefault( "priority" );
78         CheckDefault( "x", "0" );
79         CheckDefault( "y", "0" );
80         CheckDefault( "points", "(0,0)" );
81         CheckDefault( "range", "10" );
82
83         const BuilderData::Anchor anchor( atoi( attr["x"] ) + m_xOffset,
84                 atoi( attr["y"] ) + m_yOffset, atoi( attr["range"] ),
85                 atoi( attr["priority"] ), attr["points"], m_curLayoutId );
86         m_pData->m_listAnchor.push_back( anchor );
87     }
88
89     else if( rName == "Bitmap" )
90     {
91         RequireDefault( "id" );
92         RequireDefault( "file" );
93         RequireDefault( "alphacolor" );
94         CheckDefault( "nbFrames", "1" );
95         CheckDefault( "fps", "4" );
96
97         m_curBitmapId = uniqueId( attr["id"] );
98         const BuilderData::Bitmap bitmap( m_curBitmapId,
99                 attr["file"], convertColor( attr["alphacolor"] ),
100                 atoi( attr["nbFrames"] ), atoi( attr["fps"] ) );
101         m_pData->m_listBitmap.push_back( bitmap );
102     }
103
104     else if( rName == "SubBitmap" )
105     {
106         RequireDefault( "id" );
107         RequireDefault( "x" );
108         RequireDefault( "y" );
109         RequireDefault( "width" );
110         RequireDefault( "height" );
111         CheckDefault( "nbFrames", "1" );
112         CheckDefault( "fps", "4" );
113
114         const BuilderData::SubBitmap bitmap( attr["id"],
115                 m_curBitmapId, atoi( attr["x"] ), atoi( attr["y"] ),
116                 atoi( attr["width"] ), atoi( attr["height"] ),
117                 atoi( attr["nbFrames"] ), atoi( attr["fps"] ) );
118         m_pData->m_listSubBitmap.push_back( bitmap );
119     }
120
121     else if( rName == "BitmapFont" )
122     {
123         RequireDefault( "id" );
124         RequireDefault( "file" );
125         CheckDefault( "type", "digits" );
126
127         const BuilderData::BitmapFont font( attr["id"],
128                 attr["file"], attr["type"] );
129         m_pData->m_listBitmapFont.push_back( font );
130     }
131
132     else if( rName == "PopupMenu" )
133     {
134         RequireDefault( "id" );
135
136         m_popupPosList.push_back(0);
137         m_curPopupId = uniqueId( attr["id"] );
138         const BuilderData::PopupMenu popup( m_curPopupId );
139         m_pData->m_listPopupMenu.push_back( popup );
140     }
141
142     else if( rName == "MenuItem" )
143     {
144         RequireDefault( "label" );
145         CheckDefault( "action", "none" );
146
147         const BuilderData::MenuItem item( attr["label"], attr["action"],
148                                           m_popupPosList.back(),
149                                           m_curPopupId );
150         m_pData->m_listMenuItem.push_back( item );
151         m_popupPosList.back()++;
152     }
153
154     else if( rName == "MenuSeparator" )
155     {
156         const BuilderData::MenuSeparator sep( m_popupPosList.back(),
157                                               m_curPopupId );
158         m_pData->m_listMenuSeparator.push_back( sep );
159         m_popupPosList.back()++;
160     }
161
162     else if( rName == "Button" )
163     {
164         RequireDefault( "up" );
165         CheckDefault( "id", "none" );
166         CheckDefault( "visible", "true" );
167         CheckDefault( "x", "0" );
168         CheckDefault( "y", "0" );
169         CheckDefault( "lefttop", "lefttop" );
170         CheckDefault( "rightbottom", "lefttop" );
171         CheckDefault( "down", "none" );
172         CheckDefault( "over", "none" );
173         CheckDefault( "action", "none" );
174         CheckDefault( "tooltiptext", "" );
175         CheckDefault( "help", "" );
176
177         const BuilderData::Button button( uniqueId( attr["id"] ),
178                 atoi( attr["x"] ) + m_xOffset, atoi( attr["y"] ) + m_yOffset,
179                 attr["lefttop"], attr["rightbottom"], attr["visible"],
180                 attr["up"], attr["down"], attr["over"], attr["action"],
181                 attr["tooltiptext"], attr["help"],
182                 m_curLayer, m_curWindowId, m_curLayoutId );
183         m_curLayer++;
184         m_pData->m_listButton.push_back( button );
185     }
186
187     else if( rName == "Checkbox" )
188     {
189         RequireDefault( "up1" );
190         RequireDefault( "up2" );
191         RequireDefault( "state" );
192         CheckDefault( "id", "none" );
193         CheckDefault( "visible", "true" );
194         CheckDefault( "x", "0" );
195         CheckDefault( "y", "0" );
196         CheckDefault( "lefttop", "lefttop" );
197         CheckDefault( "rightbottom", "lefttop" );
198         CheckDefault( "down1", "none" );
199         CheckDefault( "over1", "none" );
200         CheckDefault( "down2", "none" );
201         CheckDefault( "over2", "none" );
202         CheckDefault( "action1", "none" );
203         CheckDefault( "action2", "none" );
204         CheckDefault( "tooltiptext1", "" );
205         CheckDefault( "tooltiptext2", "" );
206         CheckDefault( "help", "" );
207
208         const BuilderData::Checkbox checkbox( uniqueId( attr["id"] ),
209                 atoi( attr["x"] ) + m_xOffset, atoi( attr["y"] ) + m_yOffset,
210                 attr["lefttop"], attr["rightbottom"], attr["visible"],
211                 attr["up1"], attr["down1"], attr["over1"],
212                 attr["up2"], attr["down2"], attr["over2"], attr["state"],
213                 attr["action1"], attr["action2"], attr["tooltiptext1"],
214                 attr["tooltiptext2"], attr["help"], m_curLayer, m_curWindowId,
215                 m_curLayoutId );
216         m_curLayer++;
217         m_pData->m_listCheckbox.push_back( checkbox );
218     }
219
220     else if( rName == "Font" )
221     {
222         RequireDefault( "id" );
223         RequireDefault( "file" );
224         CheckDefault( "size", "12" );
225
226         const BuilderData::Font fontData( uniqueId( attr["id"] ),
227                 attr["file"], atoi( attr["size"] ) );
228         m_pData->m_listFont.push_back( fontData );
229     }
230
231     else if( rName == "Group" )
232     {
233         CheckDefault( "x", "0" );
234         CheckDefault( "y", "0" );
235
236         m_xOffset += atoi( attr["x"] );
237         m_yOffset += atoi( attr["y"] );
238         m_xOffsetList.push_back( atoi( attr["x"] ) );
239         m_yOffsetList.push_back( atoi( attr["y"] ) );
240     }
241
242     else if( rName == "Image" )
243     {
244         RequireDefault( "image" );
245         CheckDefault( "id", "none" );
246         CheckDefault( "visible", "true" );
247         CheckDefault( "x", "0" );
248         CheckDefault( "y", "0" );
249         CheckDefault( "lefttop", "lefttop" );
250         CheckDefault( "rightbottom", "lefttop" );
251         CheckDefault( "action", "none" );
252         CheckDefault( "action2", "none" );
253         CheckDefault( "resize", "mosaic" );
254         CheckDefault( "help", "" );
255
256         const BuilderData::Image imageData( uniqueId( attr["id"] ),
257                 atoi( attr["x"] ) + m_xOffset, atoi( attr["y"] ) + m_yOffset,
258                 attr["lefttop"], attr["rightbottom"], attr["visible"],
259                 attr["image"], attr["action"], attr["action2"], attr["resize"],
260                 attr["help"], m_curLayer, m_curWindowId, m_curLayoutId );
261         m_curLayer++;
262         m_pData->m_listImage.push_back( imageData );
263     }
264
265     else if( rName == "Layout" )
266     {
267         RequireDefault( "width" );
268         RequireDefault( "height" );
269         CheckDefault( "id", "none" );
270         CheckDefault( "minwidth", "-1" );
271         CheckDefault( "maxwidth", "-1" );
272         CheckDefault( "minheight", "-1" );
273         CheckDefault( "maxheight", "-1" );
274
275         m_curLayoutId = uniqueId( attr["id"] );
276         const BuilderData::Layout layout( m_curLayoutId, atoi( attr["width"] ),
277                 atoi( attr["height"] ), atoi( attr["minwidth"] ),
278                 atoi( attr["maxwidth"] ), atoi( attr["minheight"] ),
279                 atoi( attr["maxheight"] ), m_curWindowId );
280         m_pData->m_listLayout.push_back( layout );
281         m_curLayer = 0;
282     }
283
284     else if( rName == "Playlist" )
285     {
286         RequireDefault( "id" );
287         RequireDefault( "font" );
288         CheckDefault( "visible", "true" );
289         CheckDefault( "x", "0" );
290         CheckDefault( "y", "0" );
291         CheckDefault( "width", "0" );
292         CheckDefault( "height", "0" );
293         CheckDefault( "lefttop", "lefttop" );
294         CheckDefault( "rightbottom", "lefttop" );
295         CheckDefault( "bgimage", "none" );
296         CheckDefault( "fgcolor", "#000000" );
297         CheckDefault( "playcolor", "#FF0000" );
298         CheckDefault( "bgcolor1", "#FFFFFF" );
299         CheckDefault( "bgcolor2", "#FFFFFF" );
300         CheckDefault( "selcolor", "#0000FF" );
301         CheckDefault( "help", "" );
302
303         m_curListId = uniqueId( attr["id"] );
304         const BuilderData::List listData( m_curListId, atoi( attr["x"] ) +
305                 m_xOffset, atoi( attr["y"] ) + m_yOffset, attr["visible"],
306                 atoi( attr["width"]), atoi( attr["height"] ),
307                 attr["lefttop"], attr["rightbottom"],
308                 attr["font"], "playlist", attr["bgimage"],
309                 convertColor( attr["fgcolor"] ),
310                 convertColor( attr["playcolor"] ),
311                 convertColor( attr["bgcolor1"] ),
312                 convertColor( attr["bgcolor2"] ),
313                 convertColor( attr["selcolor"] ), attr["help"],
314                 m_curLayer, m_curWindowId, m_curLayoutId );
315         m_curLayer++;
316         m_pData->m_listList.push_back( listData );
317     }
318
319     else if( rName == "Playtree" )
320     {
321         RequireDefault( "id" );
322         RequireDefault( "font" );
323         CheckDefault( "visible", "true" );
324         CheckDefault( "x", "0" );
325         CheckDefault( "y", "0" );
326         CheckDefault( "width", "0" );
327         CheckDefault( "height", "0" );
328         CheckDefault( "lefttop", "lefttop" );
329         CheckDefault( "rightbottom", "lefttop" );
330         CheckDefault( "bgimage", "none" );
331         CheckDefault( "itemimage", "none" );
332         CheckDefault( "openimage", "none" );
333         CheckDefault( "closedimage", "none" );
334         CheckDefault( "fgcolor", "#000000" );
335         CheckDefault( "playcolor", "#FF0000" );
336         CheckDefault( "bgcolor1", "#FFFFFF" );
337         CheckDefault( "bgcolor2", "#FFFFFF" );
338         CheckDefault( "selcolor", "#0000FF" );
339         CheckDefault( "help", "" );
340
341         m_curTreeId = uniqueId( attr["id"] );
342         const BuilderData::Tree treeData( m_curTreeId, atoi( attr["x"] ) +
343                 m_xOffset, atoi( attr["y"] ) + m_yOffset, attr["visible"],
344                 atoi( attr["width"]), atoi( attr["height"] ),
345                 attr["lefttop"], attr["rightbottom"],
346                 attr["font"], "playtree",
347                 attr["bgimage"], attr["itemimage"],
348                 attr["openimage"], attr["closedimage"],
349                 convertColor( attr["fgcolor"] ),
350                 convertColor( attr["playcolor"] ),
351                 convertColor( attr["bgcolor1"] ),
352                 convertColor( attr["bgcolor2"] ),
353                 convertColor( attr["selcolor"] ), attr["help"],
354                 m_curLayer, m_curWindowId, m_curLayoutId );
355         m_curLayer++;
356         m_pData->m_listTree.push_back( treeData );
357     }
358
359     else if( rName == "RadialSlider" )
360     {
361         RequireDefault( "sequence" );
362         RequireDefault( "nbimages" );
363         CheckDefault( "id", "none" );
364         CheckDefault( "visible", "true" );
365         CheckDefault( "x", "0" );
366         CheckDefault( "y", "0" );
367         CheckDefault( "lefttop", "lefttop" );
368         CheckDefault( "rightbottom", "lefttop" );
369         CheckDefault( "minangle", "0" );
370         CheckDefault( "maxangle", "360" );
371         CheckDefault( "value", "none" );
372         CheckDefault( "tooltiptext", "" );
373         CheckDefault( "help", "" );
374
375         const BuilderData::RadialSlider radial( uniqueId( attr["id"] ),
376                 attr["visible"],
377                 atoi( attr["x"] ) + m_xOffset, atoi( attr["y"] ) + m_yOffset,
378                 attr["lefttop"], attr["rightbottom"], attr["sequence"],
379                 atoi( attr["nbImages"] ), atof( attr["minAngle"] ) * M_PI /180,
380                 atof( attr["maxAngle"] ) * M_PI / 180, attr["value"],
381                 attr["tooltiptext"], attr["help"], m_curLayer, m_curWindowId,
382                 m_curLayoutId );
383         m_curLayer++;
384         m_pData->m_listRadialSlider.push_back( radial );
385     }
386
387     else if( rName == "Slider" )
388     {
389         RequireDefault( "up" );
390         RequireDefault( "points" );
391         CheckDefault( "id", "none" );
392         CheckDefault( "visible", "true" );
393         CheckDefault( "x", "0" );
394         CheckDefault( "y", "0" );
395         CheckDefault( "width", "0" );
396         CheckDefault( "height", "0" );
397         CheckDefault( "lefttop", "lefttop" );
398         CheckDefault( "rightbottom", "lefttop" );
399         CheckDefault( "down", "none" );
400         CheckDefault( "over", "none" );
401         CheckDefault( "thickness", "10" );
402         CheckDefault( "value", "none" );
403         CheckDefault( "tooltiptext", "" );
404         CheckDefault( "help", "" );
405
406         string newValue = attr["value"];
407         if( m_curListId != "" )
408         {
409             // Slider associated to a list
410             newValue = "playlist.slider";
411         }
412         else if( m_curTreeId != "" )
413         {
414             // Slider associated to a tree
415             newValue = "playtree.slider";
416         }
417         const BuilderData::Slider slider( uniqueId( attr["id"] ),
418                 attr["visible"], atoi( attr["x"] ) + m_xOffset,
419                 atoi( attr["y"] ) + m_yOffset, attr["lefttop"],
420                 attr["rightbottom"], attr["up"], attr["down"],
421                 attr["over"], attr["points"], atoi( attr["thickness"] ),
422                 newValue, "none", 0, 0, 0, 0, attr["tooltiptext"],
423                 attr["help"], m_curLayer, m_curWindowId, m_curLayoutId );
424         m_curLayer++;
425         m_pData->m_listSlider.push_back( slider );
426     }
427
428     else if( rName == "SliderBackground" )
429     {
430         RequireDefault( "image" );
431         CheckDefault( "nbhoriz", "1" );
432         CheckDefault( "nbvert", "1" );
433         CheckDefault( "padhoriz", "0" );
434         CheckDefault( "padvert", "0" );
435
436         // Retrieve the current slider data
437         BuilderData::Slider &slider = m_pData->m_listSlider.back();
438
439         slider.m_imageId = attr["image"];
440         slider.m_nbHoriz = atoi( attr["nbhoriz"] );
441         slider.m_nbVert = atoi( attr["nbvert"] );
442         slider.m_padHoriz = atoi( attr["padhoriz"] );
443         slider.m_padVert = atoi( attr["padvert"] );
444     }
445
446     else if( rName == "Text" )
447     {
448         RequireDefault( "font" );
449         CheckDefault( "id", "none" );
450         CheckDefault( "visible", "true" );
451         CheckDefault( "x", "0" );
452         CheckDefault( "y", "0" );
453         CheckDefault( "text", "" );
454         CheckDefault( "color", "#000000" );
455         CheckDefault( "scrolling", "auto" );
456         CheckDefault( "alignment", "left" );
457         CheckDefault( "width", "0" );
458         CheckDefault( "lefttop", "lefttop" );
459         CheckDefault( "rightbottom", "lefttop" );
460         CheckDefault( "help", "" );
461
462         const BuilderData::Text textData( uniqueId( attr["id"] ),
463                 atoi( attr["x"] ) + m_xOffset, atoi( attr["y"] ) + m_yOffset,
464                 attr["visible"], attr["font"],
465                 attr["text"], atoi( attr["width"] ),
466                 attr["lefttop"], attr["rightbottom"],
467                 convertColor( attr["color"] ),
468                 attr["scrolling"], attr["alignment"],
469                 attr["help"], m_curLayer, m_curWindowId, m_curLayoutId );
470         m_curLayer++;
471         m_pData->m_listText.push_back( textData );
472     }
473
474     else if( rName == "Theme" )
475     {
476         RequireDefault( "version" );
477         CheckDefault( "tooltipfont", "defaultfont" );
478         CheckDefault( "magnet", "15" );
479         CheckDefault( "alpha", "255" );
480         CheckDefault( "movealpha", "255" );
481
482         // Check the version
483         if( strcmp( attr["version"], SKINS_DTD_VERSION ) )
484         {
485             msg_Err( getIntf(), "Bad theme version : %s (you need version %s)",
486                      attr["version"], SKINS_DTD_VERSION );
487             m_errors = true;
488             return;
489         }
490         const BuilderData::Theme theme( attr["tooltipfont"],
491                 atoi( attr["magnet"] ),
492                 convertInRange( attr["alpha"], 1, 255, "alpha" ),
493                 convertInRange( attr["movealpha"], 1, 255, "movealpha" ) );
494         m_pData->m_listTheme.push_back( theme );
495     }
496
497     else if( rName == "ThemeInfo" )
498     {
499         msg_Info( getIntf(), "skin: %s  author: %s", attr["name"],
500                   attr["author"] );
501     }
502
503     else if( rName == "Video" )
504     {
505         CheckDefault( "id", "none" );
506         CheckDefault( "visible", "true" );
507         CheckDefault( "x", "0" );
508         CheckDefault( "y", "0" );
509         CheckDefault( "width", "0" );
510         CheckDefault( "height", "0" );
511         CheckDefault( "lefttop", "lefttop" );
512         CheckDefault( "rightbottom", "lefttop" );
513         CheckDefault( "autoresize", "false" );
514         CheckDefault( "help", "" );
515
516         const BuilderData::Video videoData( uniqueId( attr["id"] ),
517                 atoi( attr["x"] ) + m_xOffset, atoi( attr["y"] ) + m_yOffset,
518                 atoi( attr["width"] ), atoi( attr["height" ]),
519                 attr["lefttop"], attr["rightbottom"],
520                 attr["visible"], convertBoolean( attr["autoresize"] ),
521                 attr["help"], m_curLayer, m_curWindowId, m_curLayoutId );
522         m_curLayer++;
523         m_pData->m_listVideo.push_back( videoData );
524     }
525
526     else if( rName == "Window" )
527     {
528         CheckDefault( "id", "none" );
529         CheckDefault( "visible", "true" );
530         CheckDefault( "x", "0" );
531         CheckDefault( "y", "0" );
532         CheckDefault( "dragdrop", "true" );
533         CheckDefault( "playondrop", "true" );
534
535         m_curWindowId = uniqueId( attr["id"] );
536         const BuilderData::Window window( m_curWindowId,
537                 atoi( attr["x"] ) + m_xOffset, atoi( attr["y"] ) + m_yOffset,
538                 convertBoolean( attr["visible"] ),
539                 convertBoolean( attr["dragdrop"] ),
540                 convertBoolean( attr["playondrop"] ) );
541         m_pData->m_listWindow.push_back( window );
542     }
543 }
544
545
546 void SkinParser::handleEndElement( const string &rName )
547 {
548     if( rName == "Group" )
549     {
550         m_xOffset -= m_xOffsetList.back();
551         m_yOffset -= m_yOffsetList.back();
552         m_xOffsetList.pop_back();
553         m_yOffsetList.pop_back();
554     }
555     else if( rName == "Playlist" )
556     {
557         m_curListId = "";
558     }
559     else if( rName == "Playtree" )
560     {
561         m_curTreeId = "";
562     }
563     else if( rName == "Popup" )
564     {
565         m_curPopupId = "";
566         m_popupPosList.pop_back();
567     }
568 }
569
570
571 bool SkinParser::convertBoolean( const char *value ) const
572 {
573     return strcmp( value, "true" ) == 0;
574 }
575
576
577 int SkinParser::convertColor( const char *transcolor ) const
578 {
579     unsigned long iRed, iGreen, iBlue;
580     iRed = iGreen = iBlue = 0;
581     sscanf( transcolor, "#%2lX%2lX%2lX", &iRed, &iGreen, &iBlue );
582     return ( iRed << 16 | iGreen << 8 | iBlue );
583 }
584
585
586 int SkinParser::convertInRange( const char *value, int minValue, int maxValue,
587                                 const string &rAttribute ) const
588 {
589     int intValue = atoi( value );
590
591     if( intValue < minValue )
592     {
593         msg_Warn( getIntf(), "Value of \"%s\" attribute (%i) is out of the "
594                   "expected range [%i, %i], using %i instead",
595                   rAttribute.c_str(), intValue, minValue, maxValue, minValue );
596         return minValue;
597     }
598     else if( intValue > maxValue )
599     {
600         msg_Warn( getIntf(), "Value of \"%s\" attribute (%i) is out of the "
601                   "expected range [%i, %i], using %i instead",
602                   rAttribute.c_str(), intValue, minValue, maxValue, maxValue );
603         return maxValue;
604     }
605     else
606     {
607         return intValue;
608     }
609 }
610
611
612 const string SkinParser::generateId() const
613 {
614     static int i = 1;
615
616     char genId[5];
617     snprintf( genId, 4, "%i", i++ );
618
619     string base = "_ReservedId_" + (string)genId;
620
621     return base;
622 }
623
624
625 const string SkinParser::uniqueId( const string &id )
626 {
627     string newId;
628
629     if( m_idSet.find( id ) != m_idSet.end() )
630     {
631         // The id was already used
632         if( id != "none" )
633         {
634             msg_Warn( getIntf(), "Non unique id: %s", id.c_str() );
635         }
636         newId = generateId();
637     }
638     else
639     {
640         // OK, this is a new id
641         newId = id;
642     }
643
644     // Add the id to the set
645     m_idSet.insert( newId );
646
647     return newId;
648 }