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