1 /*
2     SPDX-FileCopyrightText: 2003-2009 Sebastian Trueg <trueg@k3b.org>
3     SPDX-FileCopyrightText: 2004-2005 Jakob Petsovits <jpetso@gmx.at>
4     SPDX-FileCopyrightText: 1998-2009 Sebastian Trueg <trueg@k3b.org>
5 
6     SPDX-License-Identifier: GPL-2.0-or-later
7 */
8 
9 
10 #include "k3bpatternparser.h"
11 
12 #include <KCddb/Cdinfo>
13 
14 #include <KLocalizedString>
15 
16 #include <QDateTime>
17 #include <QLocale>
18 #include <QRegExp>
19 #include <QStack>
20 #include <QDebug>
21 
parsePattern(const KCDDB::CDInfo & entry,int trackNumber,const QString & extension,const QString & pattern,bool replace,const QString & replaceString)22 QString K3b::PatternParser::parsePattern( const KCDDB::CDInfo& entry,
23                                           int trackNumber,
24                                           const QString& extension,
25                                           const QString& pattern,
26                                           bool replace,
27                                           const QString& replaceString )
28 {
29     QString dir, s;
30     char c = ' ';     // contains the character representation of a special string
31     int len;          // length of the current special string
32 
33 
34     for( int i = 0; i < pattern.length(); ++i ) {
35 
36         if( pattern[i] == '%' ) {
37 
38             if( i + 1 < pattern.length() ) {
39                 len = 2;
40 
41                 if( pattern[i+1] != '{' ) {  // strings like %a
42                     c = pattern[i+1].toLatin1();
43                 }
44                 else if( i + 3 >= pattern.length() ) {  // too short to contain a %{*} string
45                     c = ' ';
46                 }
47                 else {  // long enough to contain %{*}
48 
49                     if( pattern[i+3] == '}' ) {  // strings like %{a}
50                         c = pattern[i+2].toLatin1();
51                         len = 4;
52                     }
53                     else {  // strings like %{artist}, or anything like %{*
54 
55                         while( i + len - 1 < pattern.length() ) {
56                             ++len;
57 
58                             if( pattern[i + len - 1] == '%' ) {  // don't touch other special strings
59                                 c = ' ';
60                                 --len;
61                                 break;
62                             }
63                             else if( pattern[i + len - 1] == '}' ) {
64                                 s = pattern.mid( i + 2, len - 3 );
65 
66                                 if( s == "title" ) {
67                                     c = TITLE;
68                                 }
69                                 else if( s == "artist" ) {
70                                     c = ARTIST;
71                                 }
72                                 else if( s == "number" ) {
73                                     c = NUMBER;
74                                 }
75                                 else if( s == "comment" ) {
76                                     c = COMMENT;
77                                 }
78                                 else if( s == "year" ) {
79                                     c = YEAR;
80                                 }
81                                 else if( s == "genre" ) {
82                                     c = GENRE;
83                                 }
84                                 else if( s == "albumtitle" ) {
85                                     c = ALBUMTITLE;
86                                 }
87                                 else if( s == "albumartist" ) {
88                                     c = ALBUMARTIST;
89                                 }
90                                 else if( s == "albumcomment" ) {
91                                     c = ALBUMCOMMENT;
92                                 }
93                                 else if( s == "date" ) {
94                                     c = DATE;
95                                 }
96                                 else if( s == "ext" ) {
97                                     c = EXTENSION;
98                                 }
99                                 else {  // no valid pattern in here, don't replace anything
100                                     c = ' ';
101                                 }
102                                 break; // finished parsing %{* string
103                             }
104                         } // end of while(...)
105 
106                     } // end of %{* strings
107 
108                 } // end of if( long enough to contain %{*} )
109 
110                 switch( c ) {
111                 case ARTIST:
112                     s = entry.track( trackNumber-1 ).get( KCDDB::Artist ).toString();
113                     s.replace( '/', '_' );
114                     s.replace( '*', '_' );
115                     s.replace( '}', '*' );  // for conditional inclusion
116                     dir.append( s.isEmpty()
117                                 ? i18n("unknown") + QString(" %1").arg(trackNumber)
118                                 : s );
119                     break;
120                 case TITLE:
121                     s = entry.track( trackNumber-1 ).get( KCDDB::Title ).toString();
122                     s = s.trimmed(); // Remove whitespace from start and the end.
123 #ifdef K3B_DEBUG
124                     qDebug() << "DEBUG:" << __PRETTY_FUNCTION__ << s;
125 #endif
126                     s.replace( '/', '_' );
127                     s.replace( '*', '_' );
128                     s.replace( '}', '*' );
129                     dir.append( s.isEmpty()
130                                 ? i18n("Track %1",trackNumber)
131                                 : s );
132                     break;
133                 case NUMBER:
134                     dir.append( QString::number(trackNumber).rightJustified( 2, '0' ) );
135                     break;
136                 case YEAR:
137                     dir.append( QString::number( entry.get( KCDDB::Year ).toInt() ) );
138                     break;
139                 case COMMENT:
140                     s = entry.track( trackNumber-1 ).get( KCDDB::Comment ).toString();
141                     s.replace( '/', '_' );
142                     s.replace( '*', '_' );
143                     s.replace( '}', '*' );
144                     dir.append( s );
145                     break;
146                 case GENRE:
147                     s = entry.get( KCDDB::Genre ).toString();
148                     if ( s.isEmpty() )
149                         s = entry.get( KCDDB::Category ).toString();
150                     s.replace( '/', '_' );
151                     s.replace( '*', '_' );
152                     s.replace( '}', '*' );
153                     dir.append( s );
154                     break;
155                 case ALBUMARTIST:
156                     s = entry.get( KCDDB::Artist ).toString();
157                     s.replace( '/', '_' );
158                     s.replace( '*', '_' );
159                     s.replace( '}', '*' );
160                     dir.append( s.isEmpty()
161                                 ? i18n("unknown") : s );
162                     break;
163                 case ALBUMTITLE:
164                     s = entry.get( KCDDB::Title ).toString();
165                     s.replace( '/', '_' );
166                     s.replace( '*', '_' );
167                     s.replace( '}', '*' );
168                     dir.append( s.isEmpty()
169                                 ? i18n("unknown") : s );
170                     break;
171                 case ALBUMCOMMENT:
172                     s = entry.get( KCDDB::Comment ).toString();
173                     s.replace( '/', '_' );
174                     s.replace( '*', '_' );
175                     s.replace( '}', '*' );
176                     dir.append( s ); // I think it makes more sense to allow empty comments
177                     break;
178                 case DATE:
179                     dir.append( QLocale().toString( QDate::currentDate() ) );
180                     break;
181                 case EXTENSION:
182                     dir.append( extension );
183                     break;
184                 default:
185                     dir.append( pattern.mid(i, len) );
186                     break;
187                 }
188                 i += len - 1;
189             }
190             else {  // end of pattern
191                 dir.append( "%" );
192             }
193         }
194         else {
195             dir.append( pattern[i] );
196         }
197     }
198 
199 
200 
201     // /* delete line comment to comment out
202     // the following part: Conditional Inclusion
203 
204     QStack<int> offsetStack;
205     QString inclusion;
206     bool isIncluded;
207 
208     static QRegExp conditionrx( "^[@|!][atyegrmx](?:='.*')?\\{" );
209     conditionrx.setMinimal( true );
210 
211     for( int i = 0; i < dir.length(); ++i ) {
212 
213         offsetStack.push(
214             conditionrx.indexIn(dir, i, QRegExp::CaretAtOffset) );
215 
216         if( offsetStack.top() == -1 ) {
217             offsetStack.pop();
218         }
219         else {
220             i += conditionrx.matchedLength() - 1;
221             continue;
222         }
223 
224         if( dir[i] == '}' && !offsetStack.isEmpty() ) {
225 
226             int offset = offsetStack.pop();
227             int length = i - offset + 1;
228 
229             switch( dir[offset+1].unicode() ) {
230             case ARTIST:
231                 s = entry.track( trackNumber-1 ).get( KCDDB::Artist ).toString();
232                 break;
233             case TITLE:
234                 s = entry.track( trackNumber-1 ).get( KCDDB::Title ).toString();
235                 break;
236             case NUMBER:
237                 s = QString::number( trackNumber );
238                 break;
239             case YEAR:
240                 s = QString::number( entry.get( KCDDB::Year ).toInt() );
241                 break;
242             case COMMENT:
243                 s = entry.track( trackNumber-1 ).get( KCDDB::Comment ).toString();
244                 break;
245             case GENRE:
246                 s = entry.get( KCDDB::Genre ).toString();
247                 if ( s.isEmpty() )
248                     s = entry.get( KCDDB::Category ).toString();
249                 break;
250             case ALBUMARTIST:
251                 s = entry.get( KCDDB::Artist ).toString();
252                 break;
253             case ALBUMTITLE:
254                 s = entry.get( KCDDB::Title ).toString();
255                 break;
256             case ALBUMCOMMENT:
257                 s = entry.get( KCDDB::Comment ).toString();
258                 break;
259             case DATE:
260                 s = QLocale().toString( QDate::currentDate() );
261                 break;
262             case EXTENSION:
263                 s = extension;
264                 break;
265             default: // we must never get here,
266                 break; // all choices should be covered
267             }
268 
269             s.replace( '/', '_' );
270             s.replace( '*', '_' );
271             s.replace( '}', '*' );
272 
273             if( dir[offset+2] == '{' ) { // no string matching, e.g. ?y{text}
274                 switch( dir[offset+1].unicode() ) {
275                 case YEAR:
276                     isIncluded = (s != "0");
277                     break;
278                 default:
279                     isIncluded = !s.isEmpty();
280                     break;
281                 }
282                 inclusion = dir.mid( offset + 3, length - 4 );
283             }
284             else { // with string matching, e.g. ?y='2004'{text}
285 
286                 // Be aware that there might be ' in the condition text
287                 int endOfCondition = dir.indexOf( '{', offset+4 )-1;
288                 QString condition = dir.mid( offset+4,
289                                              endOfCondition - (offset+4) );
290 
291                 isIncluded = (s == condition);
292                 inclusion = dir.mid( endOfCondition+2,
293                                      i - (endOfCondition+2) );
294             }
295 
296             if( dir[offset] == '!' )
297                 isIncluded = !isIncluded;
298             // Leave it when it's '@'.
299 
300             dir.replace( offset, length, ( isIncluded ? inclusion : QString("") ) );
301 
302             if( isIncluded == true )
303                 i -= length - inclusion.length();
304             else
305                 i = offset - 1; // start next loop at offset
306 
307             continue;
308 
309         } // end of replace (at closing bracket '}')
310 
311     } // end of conditional inclusion for(...)
312 
313     // end of Conditional Inclusion */
314 
315 
316     dir.replace( '*', '}' );  // bring the brackets back, if there were any
317 
318     if( replace )
319         dir.replace( QRegExp( "\\s" ), replaceString );
320 
321     if ( !dir.endsWith( '.' + extension ) )
322         dir.append( '.' + extension );
323 
324     return dir;
325 }
326