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