/** * \file importparser.cpp * Import parser. * * \b Project: Kid3 * \author Urs Fleisch * \date 17 Sep 2003 * * Copyright (C) 2003-2018 Urs Fleisch * * This file is part of Kid3. * * Kid3 is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Kid3 is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "importparser.h" #include #include #include "trackdata.h" #include "genres.h" /** * Constructor. */ ImportParser::ImportParser() : m_trackIncrNr(0), m_trackIncrEnabled(false) { } /** * Get help text for format codes supported by setFormat(). * * @param onlyRows if true only the tr elements are returned, * not the surrounding table * * @return help text. */ QString ImportParser::getFormatToolTip(bool onlyRows) { QString str; if (!onlyRows) str += QLatin1String("\n"); str += QLatin1String("\n"); str += QLatin1String("\n"); str += QLatin1String("\n"); str += QLatin1String("\n"); str += QLatin1String("\n"); str += QLatin1String("\n"); str += QLatin1String("\n"); str += QLatin1String("\n"); if (!onlyRows) str += QLatin1String("
%s%{title}"); str += QCoreApplication::translate("@default", "Title"); str += QLatin1String("
%l%{album}"); str += QCoreApplication::translate("@default", "Album"); str += QLatin1String("
%a%{artist}"); str += QCoreApplication::translate("@default", "Artist"); str += QLatin1String("
%c%{comment}"); str += QCoreApplication::translate("@default", "Comment"); str += QLatin1String("
%y%{year}"); const char* const yearStr = QT_TRANSLATE_NOOP("@default", "Year"); str += QCoreApplication::translate("@default", yearStr); str += QLatin1String("
%t%{track}"); str += QCoreApplication::translate("@default", "Track"); str += QLatin1String("
%g%{genre}"); str += QCoreApplication::translate("@default", "Genre"); str += QLatin1String("
%d%{duration}"); const char* const lengthStr = QT_TRANSLATE_NOOP("@default", "Length"); str += QCoreApplication::translate("@default", lengthStr); str += QLatin1String("
\n"); return str; } /** * Set import format. * * @param fmt format regexp * @param enableTrackIncr enable automatic track increment if no %t is found */ void ImportParser::setFormat(const QString& fmt, bool enableTrackIncr) { static const struct { const char* from; const char* to; } codeToName[] = { { "%s", "%{title}" }, { "%l", "%{album}" }, { "%a", "%{artist}" }, { "%c", "%{comment}" }, { "%y", "%{date}" }, { "%t", "%{track number}" }, { "%g", "%{genre}" }, { "%d", "%{__duration}" }, { "%f", "%{file}" }, { "%{year}", "%{date}" }, { "%{track}", "%{track number}" }, { "%{tracknumber}", "%{track number}" }, { "%{duration}", "%{__duration}" }, }; int percentIdx = 0, nr = 1, lastIdx = fmt.length() - 1; m_pattern = fmt; for (const auto& c2n : codeToName) { m_pattern.replace(QString::fromLatin1(c2n.from), QString::fromLatin1(c2n.to)); } m_codePos.clear(); while (((percentIdx = m_pattern.indexOf(QLatin1String("%{"), percentIdx)) >= 0) && (percentIdx < lastIdx)) { int closingBracePos = m_pattern.indexOf(QLatin1String("}("), percentIdx + 2); if (closingBracePos > percentIdx + 2) { QString code = m_pattern.mid(percentIdx + 2, closingBracePos - percentIdx - 2); m_codePos[code] = nr; percentIdx = closingBracePos + 2; ++nr; } else { percentIdx += 2; } } if (enableTrackIncr && !m_codePos.contains(QLatin1String("track number"))) { m_trackIncrEnabled = true; m_trackIncrNr = 1; } else { m_trackIncrEnabled = false; m_trackIncrNr = 0; } m_pattern.remove(QRegularExpression(QLatin1String("%\\{[^}]+\\}"))); m_re.setPattern(m_pattern); } /** * Get next tags in text buffer. * * @param text text buffer containing data from file or clipboard * @param frames frames for output * @param pos current position in buffer, will be updated to point * behind current match (to be used for next call) * @return true if tags found (pos is index behind match). */ bool ImportParser::getNextTags(const QString& text, TrackData& frames, int& pos) { QRegularExpressionMatch match; int idx, oldpos = pos; if (m_pattern.isEmpty()) { m_trackDuration.clear(); return false; } if (!m_codePos.contains(QLatin1String("__duration"))) { m_trackDuration.clear(); } else if (pos == 0) { m_trackDuration.clear(); int dsp = 0; // "duration search pos" int lastDsp = dsp; while ((idx = (match = m_re.match(text, dsp)).capturedStart()) != -1) { QString durationStr = match.captured(m_codePos.value(QLatin1String("__duration"))); int duration; QRegularExpression durationRe(QLatin1String("(\\d+):(\\d+)")); auto durationMatch = durationRe.match(durationStr); if (durationMatch.hasMatch()) { duration = durationMatch.captured(1).toInt() * 60 + durationMatch.captured(2).toInt(); } else { duration = durationStr.toInt(); } m_trackDuration.append(duration); dsp = idx + durationMatch.capturedLength(); if (dsp > lastDsp) { /* avoid endless loop */ lastDsp = dsp; } else { break; } } } if ((idx = (match = m_re.match(text, pos)).capturedStart()) != -1) { for (auto it = m_codePos.constBegin(); it != m_codePos.constEnd(); ++it) { const QString& name = it.key(); QString str = match.captured(*it); if (name == QLatin1String("__return")) { m_returnValues.append(str); } else if (!str.isEmpty() && !name.startsWith(QLatin1String("__"))) { if (name == QLatin1String("file")) { if (TaggedFile* taggedFile = frames.getTaggedFile()) { frames.transformToFilename(str); taggedFile->setFilenameFormattedIfEnabled(str); } } else { frames.setValue(Frame::ExtendedType(name), str); } } } if (m_trackIncrEnabled) { frames.setTrack(m_trackIncrNr++); } pos = idx + match.capturedLength(); if (pos > oldpos) { /* avoid endless loop */ return true; } } return false; }