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 }