]> git.sesse.net Git - vlc/blob - modules/gui/skins2/parser/skin_parser.cpp
21699b62b83681bbf8d1ff55d579667360a43795
[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( "flat", "false" );
325         CheckDefault( "x", "0" );
326         CheckDefault( "y", "0" );
327         CheckDefault( "width", "0" );
328         CheckDefault( "height", "0" );
329         CheckDefault( "lefttop", "lefttop" );
330         CheckDefault( "rightbottom", "lefttop" );
331         CheckDefault( "bgimage", "none" );
332         CheckDefault( "itemimage", "none" );
333         CheckDefault( "openimage", "none" );
334         CheckDefault( "closedimage", "none" );
335         CheckDefault( "fgcolor", "#000000" );
336         CheckDefault( "playcolor", "#FF0000" );
337         CheckDefault( "bgcolor1", "#FFFFFF" );
338         CheckDefault( "bgcolor2", "#FFFFFF" );
339         CheckDefault( "selcolor", "#0000FF" );
340         CheckDefault( "help", "" );
341
342         m_curTreeId = uniqueId( attr["id"] );
343         const BuilderData::Tree treeData( m_curTreeId, atoi( attr["x"] ) +
344                 m_xOffset, atoi( attr["y"] ) + m_yOffset, attr["visible"],
345                 attr["flat"],
346                 atoi( attr["width"]), atoi( attr["height"] ),
347                 attr["lefttop"], attr["rightbottom"],
348                 attr["font"], "playtree",
349                 attr["bgimage"], attr["itemimage"],
350                 attr["openimage"], attr["closedimage"],
351                 convertColor( attr["fgcolor"] ),
352                 convertColor( attr["playcolor"] ),
353                 convertColor( attr["bgcolor1"] ),
354                 convertColor( attr["bgcolor2"] ),
355                 convertColor( attr["selcolor"] ), attr["help"],
356                 m_curLayer, m_curWindowId, m_curLayoutId );
357         m_curLayer++;
358         m_pData->m_listTree.push_back( treeData );
359     }
360
361     else if( rName == "RadialSlider" )
362     {
363         RequireDefault( "sequence" );
364         RequireDefault( "nbimages" );
365         CheckDefault( "id", "none" );
366         CheckDefault( "visible", "true" );
367         CheckDefault( "x", "0" );
368         CheckDefault( "y", "0" );
369         CheckDefault( "lefttop", "lefttop" );
370         CheckDefault( "rightbottom", "lefttop" );
371         CheckDefault( "minangle", "0" );
372         CheckDefault( "maxangle", "360" );
373         CheckDefault( "value", "none" );
374         CheckDefault( "tooltiptext", "" );
375         CheckDefault( "help", "" );
376
377         const BuilderData::RadialSlider radial( uniqueId( attr["id"] ),
378                 attr["visible"],
379                 atoi( attr["x"] ) + m_xOffset, atoi( attr["y"] ) + m_yOffset,
380                 attr["lefttop"], attr["rightbottom"], attr["sequence"],
381                 atoi( attr["nbImages"] ), atof( attr["minAngle"] ) * M_PI /180,
382                 atof( attr["maxAngle"] ) * M_PI / 180, attr["value"],
383                 attr["tooltiptext"], attr["help"], m_curLayer, m_curWindowId,
384                 m_curLayoutId );
385         m_curLayer++;
386         m_pData->m_listRadialSlider.push_back( radial );
387     }
388
389     else if( rName == "Slider" )
390     {
391         RequireDefault( "up" );
392         RequireDefault( "points" );
393         CheckDefault( "id", "none" );
394         CheckDefault( "visible", "true" );
395         CheckDefault( "x", "0" );
396         CheckDefault( "y", "0" );
397         CheckDefault( "width", "0" );
398         CheckDefault( "height", "0" );
399         CheckDefault( "lefttop", "lefttop" );
400         CheckDefault( "rightbottom", "lefttop" );
401         CheckDefault( "down", "none" );
402         CheckDefault( "over", "none" );
403         CheckDefault( "thickness", "10" );
404         CheckDefault( "value", "none" );
405         CheckDefault( "tooltiptext", "" );
406         CheckDefault( "help", "" );
407
408         string newValue = attr["value"];
409         if( m_curListId != "" )
410         {
411             // Slider associated to a list
412             newValue = "playlist.slider";
413         }
414         else if( m_curTreeId != "" )
415         {
416             // Slider associated to a tree
417             newValue = "playtree.slider";
418         }
419         const BuilderData::Slider slider( uniqueId( attr["id"] ),
420                 attr["visible"], atoi( attr["x"] ) + m_xOffset,
421                 atoi( attr["y"] ) + m_yOffset, attr["lefttop"],
422                 attr["rightbottom"], attr["up"], attr["down"],
423                 attr["over"], attr["points"], atoi( attr["thickness"] ),
424                 newValue, "none", 0, 0, 0, 0, attr["tooltiptext"],
425                 attr["help"], m_curLayer, m_curWindowId, m_curLayoutId );
426         m_curLayer++;
427         m_pData->m_listSlider.push_back( slider );
428     }
429
430     else if( rName == "SliderBackground" )
431     {
432         RequireDefault( "image" );
433         CheckDefault( "nbhoriz", "1" );
434         CheckDefault( "nbvert", "1" );
435         CheckDefault( "padhoriz", "0" );
436         CheckDefault( "padvert", "0" );
437
438         // Retrieve the current slider data
439         BuilderData::Slider &slider = m_pData->m_listSlider.back();
440
441         slider.m_imageId = attr["image"];
442         slider.m_nbHoriz = atoi( attr["nbhoriz"] );
443         slider.m_nbVert = atoi( attr["nbvert"] );
444         slider.m_padHoriz = atoi( attr["padhoriz"] );
445         slider.m_padVert = atoi( attr["padvert"] );
446     }
447
448     else if( rName == "Text" )
449     {
450         RequireDefault( "font" );
451         CheckDefault( "id", "none" );
452         CheckDefault( "visible", "true" );
453         CheckDefault( "x", "0" );
454         CheckDefault( "y", "0" );
455         CheckDefault( "text", "" );
456         CheckDefault( "color", "#000000" );
457         CheckDefault( "scrolling", "auto" );
458         CheckDefault( "alignment", "left" );
459         CheckDefault( "width", "0" );
460         CheckDefault( "lefttop", "lefttop" );
461         CheckDefault( "rightbottom", "lefttop" );
462         CheckDefault( "help", "" );
463
464         const BuilderData::Text textData( uniqueId( attr["id"] ),
465                 atoi( attr["x"] ) + m_xOffset, atoi( attr["y"] ) + m_yOffset,
466                 attr["visible"], attr["font"],
467                 attr["text"], atoi( attr["width"] ),
468                 attr["lefttop"], attr["rightbottom"],
469                 convertColor( attr["color"] ),
470                 attr["scrolling"], attr["alignment"],
471                 attr["help"], m_curLayer, m_curWindowId, m_curLayoutId );
472         m_curLayer++;
473         m_pData->m_listText.push_back( textData );
474     }
475
476     else if( rName == "Theme" )
477     {
478         RequireDefault( "version" );
479         CheckDefault( "tooltipfont", "defaultfont" );
480         CheckDefault( "magnet", "15" );
481         CheckDefault( "alpha", "255" );
482         CheckDefault( "movealpha", "255" );
483
484         // Check the version
485         if( strcmp( attr["version"], SKINS_DTD_VERSION ) )
486         {
487             msg_Err( getIntf(), "Bad theme version : %s (you need version %s)",
488                      attr["version"], SKINS_DTD_VERSION );
489             m_errors = true;
490             return;
491         }
492         const BuilderData::Theme theme( attr["tooltipfont"],
493                 atoi( attr["magnet"] ),
494                 convertInRange( attr["alpha"], 1, 255, "alpha" ),
495                 convertInRange( attr["movealpha"], 1, 255, "movealpha" ) );
496         m_pData->m_listTheme.push_back( theme );
497     }
498
499     else if( rName == "ThemeInfo" )
500     {
501         msg_Info( getIntf(), "skin: %s  author: %s", attr["name"],
502                   attr["author"] );
503     }
504
505     else if( rName == "Video" )
506     {
507         CheckDefault( "id", "none" );
508         CheckDefault( "visible", "true" );
509         CheckDefault( "x", "0" );
510         CheckDefault( "y", "0" );
511         CheckDefault( "width", "0" );
512         CheckDefault( "height", "0" );
513         CheckDefault( "lefttop", "lefttop" );
514         CheckDefault( "rightbottom", "lefttop" );
515         CheckDefault( "autoresize", "false" );
516         CheckDefault( "help", "" );
517
518         const BuilderData::Video videoData( uniqueId( attr["id"] ),
519                 atoi( attr["x"] ) + m_xOffset, atoi( attr["y"] ) + m_yOffset,
520                 atoi( attr["width"] ), atoi( attr["height" ]),
521                 attr["lefttop"], attr["rightbottom"],
522                 attr["visible"], convertBoolean( attr["autoresize"] ),
523                 attr["help"], m_curLayer, m_curWindowId, m_curLayoutId );
524         m_curLayer++;
525         m_pData->m_listVideo.push_back( videoData );
526     }
527
528     else if( rName == "Window" )
529     {
530         CheckDefault( "id", "none" );
531         CheckDefault( "visible", "true" );
532         CheckDefault( "x", "0" );
533         CheckDefault( "y", "0" );
534         CheckDefault( "dragdrop", "true" );
535         CheckDefault( "playondrop", "true" );
536
537         m_curWindowId = uniqueId( attr["id"] );
538         const BuilderData::Window window( m_curWindowId,
539                 atoi( attr["x"] ) + m_xOffset, atoi( attr["y"] ) + m_yOffset,
540                 convertBoolean( attr["visible"] ),
541                 convertBoolean( attr["dragdrop"] ),
542                 convertBoolean( attr["playondrop"] ) );
543         m_pData->m_listWindow.push_back( window );
544     }
545 }
546
547
548 void SkinParser::handleEndElement( const string &rName )
549 {
550     if( rName == "Group" )
551     {
552         m_xOffset -= m_xOffsetList.back();
553         m_yOffset -= m_yOffsetList.back();
554         m_xOffsetList.pop_back();
555         m_yOffsetList.pop_back();
556     }
557     else if( rName == "Playlist" )
558     {
559         m_curListId = "";
560     }
561     else if( rName == "Playtree" )
562     {
563         m_curTreeId = "";
564     }
565     else if( rName == "Popup" )
566     {
567         m_curPopupId = "";
568         m_popupPosList.pop_back();
569     }
570 }
571
572
573 bool SkinParser::convertBoolean( const char *value ) const
574 {
575     return strcmp( value, "true" ) == 0;
576 }
577
578
579 int SkinParser::convertColor( const char *transcolor ) const
580 {
581     unsigned long iRed, iGreen, iBlue;
582     iRed = iGreen = iBlue = 0;
583     sscanf( transcolor, "#%2lX%2lX%2lX", &iRed, &iGreen, &iBlue );
584     return ( iRed << 16 | iGreen << 8 | iBlue );
585 }
586
587
588 int SkinParser::convertInRange( const char *value, int minValue, int maxValue,
589                                 const string &rAttribute ) const
590 {
591     int intValue = atoi( value );
592
593     if( intValue < minValue )
594     {
595         msg_Warn( getIntf(), "Value of \"%s\" attribute (%i) is out of the "
596                   "expected range [%i, %i], using %i instead",
597                   rAttribute.c_str(), intValue, minValue, maxValue, minValue );
598         return minValue;
599     }
600     else if( intValue > maxValue )
601     {
602         msg_Warn( getIntf(), "Value of \"%s\" attribute (%i) is out of the "
603                   "expected range [%i, %i], using %i instead",
604                   rAttribute.c_str(), intValue, minValue, maxValue, maxValue );
605         return maxValue;
606     }
607     else
608     {
609         return intValue;
610     }
611 }
612
613
614 const string SkinParser::generateId() const
615 {
616     static int i = 1;
617
618     char genId[5];
619     snprintf( genId, 4, "%i", i++ );
620
621     string base = "_ReservedId_" + (string)genId;
622
623     return base;
624 }
625
626
627 const string SkinParser::uniqueId( const string &id )
628 {
629     string newId;
630
631     if( m_idSet.find( id ) != m_idSet.end() )
632     {
633         // The id was already used
634         if( id != "none" )
635         {
636             msg_Warn( getIntf(), "Non unique id: %s", id.c_str() );
637         }
638         newId = generateId();
639     }
640     else
641     {
642         // OK, this is a new id
643         newId = id;
644     }
645
646     // Add the id to the set
647     m_idSet.insert( newId );
648
649     return newId;
650 }