File indexing completed on 2024-05-19 04:56:01

0001 /**
0002  * \file filefilter.cpp
0003  * Filter for tagged files.
0004  *
0005  * \b Project: Kid3
0006  * \author Urs Fleisch
0007  * \date 19 Jan 2008
0008  *
0009  * Copyright (C) 2008-2024  Urs Fleisch
0010  *
0011  * This file is part of Kid3.
0012  *
0013  * Kid3 is free software; you can redistribute it and/or modify
0014  * it under the terms of the GNU General Public License as published by
0015  * the Free Software Foundation; either version 2 of the License, or
0016  * (at your option) any later version.
0017  *
0018  * Kid3 is distributed in the hope that it will be useful,
0019  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0020  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0021  * GNU General Public License for more details.
0022  *
0023  * You should have received a copy of the GNU General Public License
0024  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0025  */
0026 
0027 #include "filefilter.h"
0028 #include "taggedfile.h"
0029 #include <QRegularExpression>
0030 #include <QCoreApplication>
0031 
0032 /**
0033  * Constructor.
0034  * @param parent parent object
0035  */
0036 FileFilter::FileFilter(QObject* parent) : QObject(parent),
0037   m_parser({QLatin1String("equals"), QLatin1String("contains"),
0038             QLatin1String("matches")}),
0039   m_aborted(false)
0040 {
0041 }
0042 
0043 /**
0044  * Initialize the parser.
0045  * This method has to be called before the first call to parse()
0046  * and afterwards when the expression has been changed.
0047  */
0048 void FileFilter::initParser()
0049 {
0050   m_parser.tokenizeRpn(m_filterExpression);
0051 }
0052 
0053 /**
0054  * Format a string from tag data.
0055  *
0056  * @param format format specification
0057  *
0058  * @return formatted string.
0059  */
0060 QString FileFilter::formatString(const QString& format) const
0061 {
0062   if (format.indexOf(QLatin1Char('%')) == -1) {
0063     return format;
0064   }
0065   QString str(format);
0066   str.replace(QLatin1String("%1"), QLatin1String("\v1"));
0067   str.replace(QLatin1String("%2"), QLatin1String("\v2"));
0068   str = m_trackData12.formatString(str);
0069   if (str.indexOf(QLatin1Char('\v')) != -1) {
0070     str.replace(QLatin1String("\v2"), QLatin1String("%"));
0071     str = m_trackData2.formatString(str);
0072     if (str.indexOf(QLatin1Char('\v')) != -1) {
0073       str.replace(QLatin1String("\v1"), QLatin1String("%"));
0074       str = m_trackData1.formatString(str);
0075     }
0076   }
0077   return str;
0078 }
0079 
0080 /**
0081  * Get help text for format codes supported by formatString().
0082  *
0083  * @param onlyRows if true only the tr elements are returned,
0084  *                 not the surrounding table
0085  *
0086  * @return help text.
0087  */
0088 QString FileFilter::getFormatToolTip(bool onlyRows)
0089 {
0090   QString str;
0091   if (!onlyRows) str += QLatin1String("<table>\n");
0092   str += TrackDataFormatReplacer::getToolTip(true);
0093 
0094   str += QLatin1String("<tr><td>%1a...</td><td>%1{artist}...</td><td>");
0095   str += QCoreApplication::translate("@default", "Tag 1");
0096   str += QLatin1Char(' ');
0097   str += QCoreApplication::translate("@default", "Artist");
0098   str += QLatin1String("</td></tr>\n");
0099 
0100   str += QLatin1String("<tr><td>%2a...</td><td>%2{artist}...</td><td>");
0101   str += QCoreApplication::translate("@default", "Tag 2");
0102   str += QLatin1Char(' ');
0103   str += QCoreApplication::translate("@default", "Artist");
0104   str += QLatin1String("</td></tr>\n");
0105 
0106   str += QLatin1String("<tr><td></td><td>equals</td><td>");
0107   const char* const trueIfStringsAreEqualStr =
0108       QT_TRANSLATE_NOOP("@default", "True if strings are equal");
0109   str += QCoreApplication::translate("@default", trueIfStringsAreEqualStr);
0110   str += QLatin1String("</td></tr>\n");
0111 
0112   str += QLatin1String("<tr><td></td><td>contains</td><td>");
0113   const char* const trueIfStringContainsSubstringStr =
0114       QT_TRANSLATE_NOOP("@default", "True if string contains substring");
0115   str += QCoreApplication::translate("@default",
0116                                      trueIfStringContainsSubstringStr);
0117   str += QLatin1String("</td></tr>\n");
0118 
0119   str += QLatin1String("<tr><td></td><td>matches</td><td>");
0120   const char* const trueIfStringMatchesRegexpStr =
0121       QT_TRANSLATE_NOOP("@default", "True if string matches regexp");
0122   str += QCoreApplication::translate("@default", trueIfStringMatchesRegexpStr);
0123   str += QLatin1String("</td></tr>\n");
0124 
0125   str += QLatin1String("<tr><td></td><td>and</td><td>");
0126   const char* const logicalAndStr =
0127       QT_TRANSLATE_NOOP("@default", "Logical AND");
0128   str += QCoreApplication::translate("@default", logicalAndStr);
0129   str += QLatin1String("</td></tr>\n");
0130 
0131   str += QLatin1String("<tr><td></td><td>or</td><td>");
0132   const char* const logicalOrStr = QT_TRANSLATE_NOOP("@default", "Logical OR");
0133   str += QCoreApplication::translate("@default", logicalOrStr);
0134   str += QLatin1String("</td></tr>\n");
0135 
0136   str += QLatin1String("<tr><td></td><td>not</td><td>");
0137   const char* const logicalNegationStr =
0138       QT_TRANSLATE_NOOP("@default", "Logical negation");
0139   str += QCoreApplication::translate("@default", logicalNegationStr);
0140   str += QLatin1String("</td></tr>\n");
0141 
0142   if (!onlyRows) str += QLatin1String("</table>\n");
0143   return str;
0144 }
0145 
0146 /**
0147  * Evaluate the expression to a boolean result.
0148  * @see initParser()
0149  * @return result of expression.
0150  */
0151 bool FileFilter::parse()
0152 {
0153   QString op, var1, var2;
0154   bool result = false;
0155   m_parser.clearEvaluation();
0156   while (m_parser.evaluate(op, var1, var2)) {
0157     var1 = formatString(var1);
0158     var2 = formatString(var2);
0159     if (op == QLatin1String("equals")) {
0160       m_parser.pushBool(var1 == var2);
0161     } else if (op == QLatin1String("contains")) {
0162       m_parser.pushBool(var2.indexOf(var1) >= 0);
0163     } else if (op == QLatin1String("matches")) {
0164       m_parser.pushBool(QRegularExpression(var1).match(var2).hasMatch());
0165     }
0166   }
0167   if (!m_parser.hasError()) {
0168     m_parser.popBool(result);
0169   }
0170   return result;
0171 }
0172 
0173 /**
0174  * Check if file passes through filter.
0175  *
0176  * @param taggedFile file to check
0177  * @param ok         if not 0, false is returned here when parsing fails
0178  *
0179  * @return true if file passes through filter.
0180  */
0181 bool FileFilter::filter(TaggedFile& taggedFile, bool* ok)
0182 {
0183   if (m_filterExpression.isEmpty()) {
0184     if (ok) *ok = true;
0185     return true;
0186   }
0187   m_trackData1 = ImportTrackData(taggedFile, Frame::TagV1);
0188   m_trackData2 = ImportTrackData(taggedFile, Frame::TagV2);
0189   m_trackData12 = ImportTrackData(taggedFile, Frame::TagV2V1);
0190 
0191   bool result = parse();
0192   if (m_parser.hasError()) {
0193     if (ok) *ok = false;
0194     return false;
0195   }
0196   if (ok) *ok = true;
0197   return result;
0198 }
0199 
0200 /**
0201  * Clear abort flag.
0202  */
0203 void FileFilter::clearAborted()
0204 {
0205   m_aborted = false;
0206 }
0207 
0208 /**
0209  * Check if dialog was aborted.
0210  * @return true if aborted.
0211  */
0212 bool FileFilter::isAborted() const
0213 {
0214   return m_aborted;
0215 }
0216 
0217 /**
0218  * Set abort flag.
0219  */
0220 void FileFilter::abort()
0221 {
0222   m_aborted = true;
0223 }