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