File indexing completed on 2024-04-28 04:50:22
0001 /* 0002 SPDX-FileCopyrightText: 2003-2009 Sebastian Trueg <trueg@k3b.org> 0003 SPDX-FileCopyrightText: 2004-2005 Jakob Petsovits <jpetso@gmx.at> 0004 SPDX-FileCopyrightText: 1998-2009 Sebastian Trueg <trueg@k3b.org> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 0010 #include "k3bpatternparser.h" 0011 0012 #include <KCDDB/CDInfo> 0013 0014 #include <KLocalizedString> 0015 0016 #include <QDateTime> 0017 #include <QLocale> 0018 #include <QRegExp> 0019 #include <QRegularExpression> 0020 #include <QStack> 0021 #include <QDebug> 0022 0023 static void replaceControlCharacters( QString& s ) 0024 { 0025 s.replace( '/', '_' ); 0026 s.replace( '*', '_' ); 0027 s.replace( '}', '*' ); // for conditional inclusion 0028 } 0029 0030 QString K3b::PatternParser::parsePattern( const KCDDB::CDInfo& entry, 0031 int trackNumber, 0032 const QString& extension, 0033 const QString& pattern, 0034 bool replace, 0035 const QString& replaceString ) 0036 { 0037 QString dir, s; 0038 char c = ' '; // contains the character representation of a special string 0039 int len; // length of the current special string 0040 0041 0042 for( int i = 0; i < pattern.length(); ++i ) { 0043 0044 if( pattern[i] == '%' ) { 0045 0046 if( i + 1 < pattern.length() ) { 0047 len = 2; 0048 0049 if( pattern[i+1] != '{' ) { // strings like %a 0050 c = pattern[i+1].toLatin1(); 0051 } 0052 else if( i + 3 >= pattern.length() ) { // too short to contain a %{*} string 0053 c = ' '; 0054 } 0055 else { // long enough to contain %{*} 0056 0057 if( pattern[i+3] == '}' ) { // strings like %{a} 0058 c = pattern[i+2].toLatin1(); 0059 len = 4; 0060 } 0061 else { // strings like %{artist}, or anything like %{* 0062 0063 while( i + len - 1 < pattern.length() ) { 0064 ++len; 0065 0066 if( pattern[i + len - 1] == '%' ) { // don't touch other special strings 0067 c = ' '; 0068 --len; 0069 break; 0070 } 0071 else if( pattern[i + len - 1] == '}' ) { 0072 s = pattern.mid( i + 2, len - 3 ); 0073 0074 if( s == "title" ) { 0075 c = TITLE; 0076 } 0077 else if( s == "artist" ) { 0078 c = ARTIST; 0079 } 0080 else if( s == "number" ) { 0081 c = NUMBER; 0082 } 0083 else if( s == "comment" ) { 0084 c = COMMENT; 0085 } 0086 else if( s == "year" ) { 0087 c = YEAR; 0088 } 0089 else if( s == "genre" ) { 0090 c = GENRE; 0091 } 0092 else if( s == "albumtitle" ) { 0093 c = ALBUMTITLE; 0094 } 0095 else if( s == "albumartist" ) { 0096 c = ALBUMARTIST; 0097 } 0098 else if( s == "albumcomment" ) { 0099 c = ALBUMCOMMENT; 0100 } 0101 else if( s == "date" ) { 0102 c = DATE; 0103 } 0104 else if( s == "ext" ) { 0105 c = EXTENSION; 0106 } 0107 else { // no valid pattern in here, don't replace anything 0108 c = ' '; 0109 } 0110 break; // finished parsing %{* string 0111 } 0112 } // end of while(...) 0113 0114 } // end of %{* strings 0115 0116 } // end of if( long enough to contain %{*} ) 0117 0118 switch( c ) { 0119 case ARTIST: 0120 s = entry.track( trackNumber-1 ).get( KCDDB::Artist ).toString(); 0121 replaceControlCharacters( s ); 0122 dir.append( s.isEmpty() 0123 ? i18n("unknown") + QString(" %1").arg(trackNumber) 0124 : s ); 0125 break; 0126 case TITLE: 0127 s = entry.track( trackNumber-1 ).get( KCDDB::Title ).toString(); 0128 s = s.trimmed(); // Remove whitespace from start and the end. 0129 #ifdef K3B_DEBUG 0130 qDebug() << "DEBUG:" << __PRETTY_FUNCTION__ << s; 0131 #endif 0132 replaceControlCharacters( s ); 0133 dir.append( s.isEmpty() 0134 ? i18n("Track %1",trackNumber) 0135 : s ); 0136 break; 0137 case NUMBER: 0138 dir.append( QString::number(trackNumber).rightJustified( 2, '0' ) ); 0139 break; 0140 case YEAR: 0141 dir.append( QString::number( entry.get( KCDDB::Year ).toInt() ) ); 0142 break; 0143 case COMMENT: 0144 s = entry.track( trackNumber-1 ).get( KCDDB::Comment ).toString(); 0145 replaceControlCharacters( s ); 0146 dir.append( s ); 0147 break; 0148 case GENRE: 0149 s = entry.get( KCDDB::Genre ).toString(); 0150 if ( s.isEmpty() ) 0151 s = entry.get( KCDDB::Category ).toString(); 0152 replaceControlCharacters( s ); 0153 dir.append( s ); 0154 break; 0155 case ALBUMARTIST: 0156 s = entry.get( KCDDB::Artist ).toString(); 0157 replaceControlCharacters( s ); 0158 dir.append( s.isEmpty() 0159 ? i18n("unknown") : s ); 0160 break; 0161 case ALBUMTITLE: 0162 s = entry.get( KCDDB::Title ).toString(); 0163 replaceControlCharacters( s ); 0164 dir.append( s.isEmpty() 0165 ? i18n("unknown") : s ); 0166 break; 0167 case ALBUMCOMMENT: 0168 s = entry.get( KCDDB::Comment ).toString(); 0169 replaceControlCharacters( s ); 0170 dir.append( s ); // I think it makes more sense to allow empty comments 0171 break; 0172 case DATE: 0173 dir.append( QLocale().toString( QDate::currentDate() ) ); 0174 break; 0175 case EXTENSION: 0176 dir.append( extension ); 0177 break; 0178 default: 0179 dir.append( pattern.mid(i, len) ); 0180 break; 0181 } 0182 i += len - 1; 0183 } 0184 else { // end of pattern 0185 dir.append( "%" ); 0186 } 0187 } 0188 else { 0189 dir.append( pattern[i] ); 0190 } 0191 } 0192 0193 0194 0195 // /* delete line comment to comment out 0196 // the following part: Conditional Inclusion 0197 0198 QStack<int> offsetStack; 0199 QString inclusion; 0200 bool isIncluded; 0201 0202 static QRegExp conditionrx( "^[@|!][atyegrmx](?:='.*')?\\{" ); 0203 conditionrx.setMinimal( true ); 0204 0205 for( int i = 0; i < dir.length(); ++i ) { 0206 0207 offsetStack.push( 0208 conditionrx.indexIn(dir, i, QRegExp::CaretAtOffset) ); 0209 0210 if( offsetStack.top() == -1 ) { 0211 offsetStack.pop(); 0212 } 0213 else { 0214 i += conditionrx.matchedLength() - 1; 0215 continue; 0216 } 0217 0218 if( dir[i] == '}' && !offsetStack.isEmpty() ) { 0219 0220 int offset = offsetStack.pop(); 0221 int length = i - offset + 1; 0222 0223 switch( dir[offset+1].unicode() ) { 0224 case ARTIST: 0225 s = entry.track( trackNumber-1 ).get( KCDDB::Artist ).toString(); 0226 break; 0227 case TITLE: 0228 s = entry.track( trackNumber-1 ).get( KCDDB::Title ).toString(); 0229 break; 0230 case NUMBER: 0231 s = QString::number( trackNumber ); 0232 break; 0233 case YEAR: 0234 s = QString::number( entry.get( KCDDB::Year ).toInt() ); 0235 break; 0236 case COMMENT: 0237 s = entry.track( trackNumber-1 ).get( KCDDB::Comment ).toString(); 0238 break; 0239 case GENRE: 0240 s = entry.get( KCDDB::Genre ).toString(); 0241 if ( s.isEmpty() ) 0242 s = entry.get( KCDDB::Category ).toString(); 0243 break; 0244 case ALBUMARTIST: 0245 s = entry.get( KCDDB::Artist ).toString(); 0246 break; 0247 case ALBUMTITLE: 0248 s = entry.get( KCDDB::Title ).toString(); 0249 break; 0250 case ALBUMCOMMENT: 0251 s = entry.get( KCDDB::Comment ).toString(); 0252 break; 0253 case DATE: 0254 s = QLocale().toString( QDate::currentDate() ); 0255 break; 0256 case EXTENSION: 0257 s = extension; 0258 break; 0259 default: // we must never get here, 0260 break; // all choices should be covered 0261 } 0262 0263 s.replace( '/', '_' ); 0264 s.replace( '*', '_' ); 0265 s.replace( '}', '*' ); 0266 0267 if( dir[offset+2] == '{' ) { // no string matching, e.g. ?y{text} 0268 switch( dir[offset+1].unicode() ) { 0269 case YEAR: 0270 isIncluded = (s != "0"); 0271 break; 0272 default: 0273 isIncluded = !s.isEmpty(); 0274 break; 0275 } 0276 inclusion = dir.mid( offset + 3, length - 4 ); 0277 } 0278 else { // with string matching, e.g. ?y='2004'{text} 0279 0280 // Be aware that there might be ' in the condition text 0281 int endOfCondition = dir.indexOf( '{', offset+4 )-1; 0282 QString condition = dir.mid( offset+4, 0283 endOfCondition - (offset+4) ); 0284 0285 isIncluded = (s == condition); 0286 inclusion = dir.mid( endOfCondition+2, 0287 i - (endOfCondition+2) ); 0288 } 0289 0290 if( dir[offset] == '!' ) 0291 isIncluded = !isIncluded; 0292 // Leave it when it's '@'. 0293 0294 dir.replace( offset, length, ( isIncluded ? inclusion : QString("") ) ); 0295 0296 if( isIncluded == true ) 0297 i -= length - inclusion.length(); 0298 else 0299 i = offset - 1; // start next loop at offset 0300 0301 continue; 0302 0303 } // end of replace (at closing bracket '}') 0304 0305 } // end of conditional inclusion for(...) 0306 0307 // end of Conditional Inclusion */ 0308 0309 0310 dir.replace( '*', '}' ); // bring the brackets back, if there were any 0311 0312 if( replace ) { 0313 static const QRegularExpression rx( "\\s" ); 0314 dir.replace( rx, replaceString ); 0315 } 0316 0317 if ( !dir.endsWith( '.' + extension ) ) 0318 dir.append( '.' + extension ); 0319 0320 return dir; 0321 }