1 /**
2 * \file importparser.cpp
3 * Import parser.
4 *
5 * \b Project: Kid3
6 * \author Urs Fleisch
7 * \date 17 Sep 2003
8 *
9 * Copyright (C) 2003-2018 Urs Fleisch
10 *
11 * This file is part of Kid3.
12 *
13 * Kid3 is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * Kid3 is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 */
26
27 #include "importparser.h"
28 #include <QCoreApplication>
29 #include <QRegularExpression>
30 #include "trackdata.h"
31 #include "genres.h"
32
33 /**
34 * Constructor.
35 */
ImportParser()36 ImportParser::ImportParser() : m_trackIncrNr(0), m_trackIncrEnabled(false)
37 {
38 }
39
40 /**
41 * Get help text for format codes supported by setFormat().
42 *
43 * @param onlyRows if true only the tr elements are returned,
44 * not the surrounding table
45 *
46 * @return help text.
47 */
getFormatToolTip(bool onlyRows)48 QString ImportParser::getFormatToolTip(bool onlyRows)
49 {
50 QString str;
51 if (!onlyRows) str += QLatin1String("<table>\n");
52
53 str += QLatin1String("<tr><td>%s</td><td>%{title}</td><td>");
54 str += QCoreApplication::translate("@default", "Title");
55 str += QLatin1String("</td></tr>\n");
56
57 str += QLatin1String("<tr><td>%l</td><td>%{album}</td><td>");
58 str += QCoreApplication::translate("@default", "Album");
59 str += QLatin1String("</td></tr>\n");
60
61 str += QLatin1String("<tr><td>%a</td><td>%{artist}</td><td>");
62 str += QCoreApplication::translate("@default", "Artist");
63 str += QLatin1String("</td></tr>\n");
64
65 str += QLatin1String("<tr><td>%c</td><td>%{comment}</td><td>");
66 str += QCoreApplication::translate("@default", "Comment");
67 str += QLatin1String("</td></tr>\n");
68
69 str += QLatin1String("<tr><td>%y</td><td>%{year}</td><td>");
70 const char* const yearStr = QT_TRANSLATE_NOOP("@default", "Year");
71 str += QCoreApplication::translate("@default", yearStr);
72 str += QLatin1String("</td></tr>\n");
73
74 str += QLatin1String("<tr><td>%t</td><td>%{track}</td><td>");
75 str += QCoreApplication::translate("@default", "Track");
76 str += QLatin1String("</td></tr>\n");
77
78 str += QLatin1String("<tr><td>%g</td><td>%{genre}</td><td>");
79 str += QCoreApplication::translate("@default", "Genre");
80 str += QLatin1String("</td></tr>\n");
81
82 str += QLatin1String("<tr><td>%d</td><td>%{duration}</td><td>");
83 const char* const lengthStr = QT_TRANSLATE_NOOP("@default", "Length");
84 str += QCoreApplication::translate("@default", lengthStr);
85 str += QLatin1String("</td></tr>\n");
86
87 if (!onlyRows) str += QLatin1String("</table>\n");
88 return str;
89 }
90
91 /**
92 * Set import format.
93 *
94 * @param fmt format regexp
95 * @param enableTrackIncr enable automatic track increment if no %t is found
96 */
setFormat(const QString & fmt,bool enableTrackIncr)97 void ImportParser::setFormat(const QString& fmt, bool enableTrackIncr)
98 {
99 static const struct {
100 const char* from;
101 const char* to;
102 } codeToName[] = {
103 { "%s", "%{title}" },
104 { "%l", "%{album}" },
105 { "%a", "%{artist}" },
106 { "%c", "%{comment}" },
107 { "%y", "%{date}" },
108 { "%t", "%{track number}" },
109 { "%g", "%{genre}" },
110 { "%d", "%{__duration}" },
111 { "%f", "%{file}" },
112 { "%{year}", "%{date}" },
113 { "%{track}", "%{track number}" },
114 { "%{tracknumber}", "%{track number}" },
115 { "%{duration}", "%{__duration}" },
116 };
117 int percentIdx = 0, nr = 1, lastIdx = fmt.length() - 1;
118 m_pattern = fmt;
119 for (const auto& c2n : codeToName) {
120 m_pattern.replace(QString::fromLatin1(c2n.from), QString::fromLatin1(c2n.to));
121 }
122
123 m_codePos.clear();
124 while (((percentIdx = m_pattern.indexOf(QLatin1String("%{"), percentIdx)) >= 0) &&
125 (percentIdx < lastIdx)) {
126 int closingBracePos = m_pattern.indexOf(QLatin1String("}("), percentIdx + 2);
127 if (closingBracePos > percentIdx + 2) {
128 QString code =
129 m_pattern.mid(percentIdx + 2, closingBracePos - percentIdx - 2);
130 m_codePos[code] = nr;
131 percentIdx = closingBracePos + 2;
132 ++nr;
133 } else {
134 percentIdx += 2;
135 }
136 }
137
138 if (enableTrackIncr && !m_codePos.contains(QLatin1String("track number"))) {
139 m_trackIncrEnabled = true;
140 m_trackIncrNr = 1;
141 } else {
142 m_trackIncrEnabled = false;
143 m_trackIncrNr = 0;
144 }
145
146 m_pattern.remove(QRegularExpression(QLatin1String("%\\{[^}]+\\}")));
147 m_re.setPattern(m_pattern);
148 }
149
150 /**
151 * Get next tags in text buffer.
152 *
153 * @param text text buffer containing data from file or clipboard
154 * @param frames frames for output
155 * @param pos current position in buffer, will be updated to point
156 * behind current match (to be used for next call)
157 * @return true if tags found (pos is index behind match).
158 */
getNextTags(const QString & text,TrackData & frames,int & pos)159 bool ImportParser::getNextTags(const QString& text, TrackData& frames, int& pos)
160 {
161 QRegularExpressionMatch match;
162 int idx, oldpos = pos;
163 if (m_pattern.isEmpty()) {
164 m_trackDuration.clear();
165 return false;
166 }
167 if (!m_codePos.contains(QLatin1String("__duration"))) {
168 m_trackDuration.clear();
169 } else if (pos == 0) {
170 m_trackDuration.clear();
171 int dsp = 0; // "duration search pos"
172 int lastDsp = dsp;
173 while ((idx = (match = m_re.match(text, dsp)).capturedStart()) != -1) {
174 QString durationStr = match.captured(m_codePos.value(QLatin1String("__duration")));
175 int duration;
176 QRegularExpression durationRe(QLatin1String("(\\d+):(\\d+)"));
177 auto durationMatch = durationRe.match(durationStr);
178 if (durationMatch.hasMatch()) {
179 duration = durationMatch.captured(1).toInt() * 60 +
180 durationMatch.captured(2).toInt();
181 } else {
182 duration = durationStr.toInt();
183 }
184 m_trackDuration.append(duration);
185
186 dsp = idx + durationMatch.capturedLength();
187 if (dsp > lastDsp) { /* avoid endless loop */
188 lastDsp = dsp;
189 } else {
190 break;
191 }
192 }
193 }
194 if ((idx = (match = m_re.match(text, pos)).capturedStart()) != -1) {
195 for (auto it = m_codePos.constBegin(); it != m_codePos.constEnd(); ++it) {
196 const QString& name = it.key();
197 QString str = match.captured(*it);
198 if (name == QLatin1String("__return")) {
199 m_returnValues.append(str);
200 } else if (!str.isEmpty() && !name.startsWith(QLatin1String("__"))) {
201 if (name == QLatin1String("file")) {
202 if (TaggedFile* taggedFile = frames.getTaggedFile()) {
203 frames.transformToFilename(str);
204 taggedFile->setFilenameFormattedIfEnabled(str);
205 }
206 } else {
207 frames.setValue(Frame::ExtendedType(name), str);
208 }
209 }
210 }
211 if (m_trackIncrEnabled) {
212 frames.setTrack(m_trackIncrNr++);
213 }
214 pos = idx + match.capturedLength();
215 if (pos > oldpos) { /* avoid endless loop */
216 return true;
217 }
218 }
219 return false;
220 }
221