File indexing completed on 2025-03-09 03:57:05

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2009-08-11
0007  * Description : the main parser object for the AdvancedRename utility
0008  *
0009  * SPDX-FileCopyrightText: 2009-2012 by Andi Clemens <andi dot clemens at gmail dot com>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "parser.h"
0016 
0017 // Qt includes
0018 
0019 #include <QFileInfo>
0020 #include <QRegularExpression>
0021 
0022 // Local includes
0023 
0024 #include "cameranameoption.h"
0025 #include "databaseoption.h"
0026 #include "dateoption.h"
0027 #include "directorynameoption.h"
0028 #include "filepropertiesoption.h"
0029 #include "metadataoption.h"
0030 #include "sequencenumberoption.h"
0031 #include "casemodifier.h"
0032 #include "defaultvaluemodifier.h"
0033 #include "rangemodifier.h"
0034 #include "removedoublesmodifier.h"
0035 #include "replacemodifier.h"
0036 #include "trimmedmodifier.h"
0037 #include "uniquemodifier.h"
0038 
0039 namespace Digikam
0040 {
0041 
0042 class Q_DECL_HIDDEN Parser::Private
0043 {
0044 public:
0045 
0046     explicit Private()
0047     {
0048     }
0049 
0050     RulesList options;
0051     RulesList modifiers;
0052 };
0053 
0054 // --------------------------------------------------------
0055 
0056 Parser::Parser()
0057     : d(new Private)
0058 {
0059     registerOption(new FilePropertiesOption());
0060     registerOption(new DirectoryNameOption());
0061     registerOption(new CameraNameOption());
0062     registerOption(new SequenceNumberOption());
0063     registerOption(new DateOption());
0064     registerOption(new DatabaseOption());
0065     registerOption(new MetadataOption());
0066 
0067     // --------------------------------------------------------
0068 
0069     registerModifier(new CaseModifier());
0070     registerModifier(new TrimmedModifier());
0071     registerModifier(new UniqueModifier());
0072     registerModifier(new RemoveDoublesModifier());
0073     registerModifier(new DefaultValueModifier());
0074     registerModifier(new ReplaceModifier());
0075     registerModifier(new RangeModifier());
0076 }
0077 
0078 Parser::~Parser()
0079 {
0080     qDeleteAll(d->options);
0081     d->options.clear();
0082 
0083     qDeleteAll(d->modifiers);
0084     d->modifiers.clear();
0085 
0086     delete d;
0087 }
0088 
0089 void Parser::reset()
0090 {
0091     Q_FOREACH (Rule* const option, d->options)
0092     {
0093         option->reset();
0094     }
0095 
0096     Q_FOREACH (Rule* const modifier, d->modifiers)
0097     {
0098         modifier->reset();
0099     }
0100 }
0101 
0102 bool Parser::parseStringIsValid(const QString& str)
0103 {
0104     QRegularExpression invalidString(QRegularExpression::anchoredPattern(QLatin1String("^\\s*$")));
0105     return (!str.isEmpty() && !invalidString.match(str).hasMatch());
0106 }
0107 
0108 RulesList Parser::options() const
0109 {
0110     return d->options;
0111 }
0112 
0113 RulesList Parser::modifiers() const
0114 {
0115     return d->modifiers;
0116 }
0117 
0118 void Parser::registerOption(Rule* option)
0119 {
0120     if (!option || !option->isValid())
0121     {
0122         return;
0123     }
0124 
0125     d->options.append(option);
0126 }
0127 
0128 void Parser::unregisterOption(const Rule* option)
0129 {
0130     if (!option)
0131     {
0132         return;
0133     }
0134 
0135     for (RulesList::iterator it = d->options.begin() ;
0136          it != d->options.end() ; )
0137     {
0138         if (*it == option)
0139         {
0140             delete *it;
0141             it = d->options.erase(it);
0142         }
0143         else
0144         {
0145             ++it;
0146         }
0147     }
0148 }
0149 
0150 void Parser::registerModifier(Rule* modifier)
0151 {
0152     if (!modifier || !modifier->isValid())
0153     {
0154         return;
0155     }
0156 
0157     d->modifiers.append(modifier);
0158 }
0159 
0160 void Parser::unregisterModifier(const Rule* modifier)
0161 {
0162     if (!modifier)
0163     {
0164         return;
0165     }
0166 
0167     for (RulesList::iterator it = d->modifiers.begin() ;
0168          it != d->modifiers.end() ; )
0169     {
0170         if (*it == modifier)
0171         {
0172             delete *it;
0173             it = d->modifiers.erase(it);
0174         }
0175         else
0176         {
0177             ++it;
0178         }
0179     }
0180 }
0181 
0182 ParseResults Parser::results(ParseSettings& settings)
0183 {
0184     ParseResults results;
0185 
0186     Q_FOREACH (Rule* const option, d->options)
0187     {
0188         ParseResults r = option->parse(settings);
0189         results.append(r);
0190     }
0191 
0192     Q_FOREACH (Rule* const modifier, d->modifiers)
0193     {
0194         ParseResults r = modifier->parse(settings);
0195         results.append(r);
0196     }
0197 
0198     return results;
0199 }
0200 
0201 ParseResults Parser::invalidModifiers(ParseSettings& settings)
0202 {
0203     parse(settings);
0204 
0205     return settings.invalidModifiers;
0206 }
0207 
0208 QString Parser::parse(ParseSettings& settings)
0209 {
0210     QFileInfo fi(settings.fileUrl.toLocalFile());
0211 
0212     if (!parseStringIsValid(settings.parseString))
0213     {
0214         return fi.fileName();
0215     }
0216 
0217     ParseResults results;
0218 
0219     Q_FOREACH (Rule* const option, d->options)
0220     {
0221         ParseResults r = option->parse(settings);
0222         results.append(r);
0223     }
0224 
0225     settings.invalidModifiers = applyModifiers(settings, results);
0226     QString newName           = results.replaceTokens(settings.parseString);
0227     settings.results          = results;
0228 
0229     // remove invalid modifiers from the new name
0230 
0231     Q_FOREACH (Rule* const mod, d->modifiers)
0232     {
0233         newName.remove(mod->regExp());
0234     }
0235 
0236     if (newName.isEmpty())
0237     {
0238         return fi.fileName();
0239     }
0240 
0241     if (settings.useOriginalFileExtension)
0242     {
0243         newName.append(QLatin1Char('.')).append(fi.suffix());
0244     }
0245 
0246     return newName;
0247 }
0248 
0249 bool Parser::tokenAtPosition(ParseSettings& settings, int pos)
0250 {
0251     int start;
0252     int length;
0253     return tokenAtPosition(settings, pos, start, length);
0254 }
0255 
0256 bool Parser::tokenAtPosition(ParseSettings& settings, int pos, int& start, int& length)
0257 {
0258     bool found                   = false;
0259     ParseResults r               = results(settings);
0260     ParseResults::ResultsKey key = r.keyAtApproximatePosition(pos);
0261     start                        = key.first;
0262     length                       = key.second;
0263 
0264     if ((pos >= start) && (pos <= start + length))
0265     {
0266         found = true;
0267     }
0268 
0269     return found;
0270 }
0271 
0272 ParseResults Parser::applyModifiers(const ParseSettings& _settings, ParseResults& results)
0273 {
0274     if (results.isEmpty() || d->modifiers.isEmpty())
0275     {
0276         return ParseResults();
0277     }
0278 
0279     ParseSettings settings = _settings;
0280     settings.results       = results;
0281 
0282     // appliedModifiers holds all the modified parse results
0283 
0284     ParseResults appliedModifiers = results;
0285 
0286     // modifierResults holds all the modifiers found in the parse string
0287 
0288     ParseResults modifierResults;
0289 
0290     // modifierMap maps the actual modifier objects to the entries in the modifierResults structure
0291 
0292     QMap<ParseResults::ResultsKey, Rule*> modifierMap;
0293 
0294     Q_FOREACH (Rule* const modifier, d->modifiers)
0295     {
0296         QRegularExpression regExp = modifier->regExp();
0297         int pos                   = 0;
0298         QRegularExpressionMatch match;
0299 
0300         while (pos > -1)
0301         {
0302             pos = _settings.parseString.indexOf(regExp, pos, &match);
0303 
0304             if (pos > -1)
0305             {
0306                 ParseResults::ResultsKey   k(pos, match.capturedLength());
0307                 ParseResults::ResultsValue v(match.captured(0), QString());
0308 
0309                 modifierResults.addEntry(k, v);
0310                 modifierMap.insert(k, modifier);
0311 
0312                 pos += match.capturedLength();
0313             }
0314         }
0315     }
0316 
0317     // Check for valid modifiers (they must appear directly after an option) and apply the modification to the option
0318     // parse result.
0319     // We need to create a second ParseResults object with modified keys, otherwise the final parsing step will not
0320     // remove the modifier tokens from the result.
0321 
0322     Q_FOREACH (const ParseResults::ResultsKey& key, results.keys())
0323     {
0324         int off  = results.offset(key);
0325         int diff = 0;
0326 
0327         for (int pos = off ; pos < _settings.parseString.count() ; )
0328         {
0329             if (modifierResults.hasKeyAtPosition(pos))
0330             {
0331                 ParseResults::ResultsKey mkey = modifierResults.keyAtPosition(pos);
0332                 Rule* const mod               = modifierMap[mkey];
0333                 QString modToken              = modifierResults.token(mkey);
0334 
0335                 QString token                 = results.token(key);
0336                 QString str2Modify            = results.result(key);
0337 
0338                 QString modResult;
0339 
0340                 if (mod)
0341                 {
0342                     settings.parseString       = modToken;
0343                     settings.currentResultsKey = key;
0344                     settings.str2Modify        = str2Modify;
0345                     ParseResults modResults    = mod->parse(settings);
0346 
0347                     if (!modResults.isEmpty() && (modResults.values().length() == 1))
0348                     {
0349                         modResult = modResults.result(modResults.keys().constFirst());
0350                     }
0351                 }
0352 
0353                 // update result
0354 
0355                 ParseResults::ResultsKey   resultKey = key;
0356                 ParseResults::ResultsValue resultValue(token, modResult);
0357                 results.addEntry(resultKey, resultValue);
0358 
0359                 // update modifier map
0360 
0361                 ParseResults::ResultsKey modifierKey = key;
0362                 modifierKey.second                  += diff;
0363                 ParseResults::ResultsValue modifierValue(modToken, modResult);
0364 
0365                 appliedModifiers.deleteEntry(modifierKey);
0366                 modifierKey.second += modToken.count();
0367                 appliedModifiers.addEntry(modifierKey, modifierValue);
0368 
0369                 // set position to the next possible token
0370 
0371                 pos  += mkey.second;
0372                 diff += mkey.second;
0373 
0374                 // remove assigned modifier from modifierResults
0375 
0376                 modifierResults.deleteEntry(mkey);
0377             }
0378             else
0379             {
0380                 break;
0381             }
0382         }
0383     }
0384 
0385     results = appliedModifiers;
0386 
0387     return modifierResults;
0388 }
0389 
0390 } // namespace Digikam