1 /*************************************************************************
2 AsciiDecoder.cpp - decoder for ASCII data
3 -------------------
4 begin : Sun Dec 03 2006
5 copyright : (C) 2006 by Thomas Eschenbacher
6 email : Thomas.Eschenbacher@gmx.de
7 ***************************************************************************/
8
9 /***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17 #include "config.h"
18
19 #include <ctype.h>
20 #include <string.h>
21
22 #include <new>
23
24 #include <QDateTime>
25 #include <QLatin1Char>
26 #include <QLatin1String>
27 #include <QRegExp>
28
29 #include <KLocalizedString>
30
31 #include "libkwave/Label.h"
32 #include "libkwave/LabelList.h"
33 #include "libkwave/MessageBox.h"
34 #include "libkwave/MultiWriter.h"
35 #include "libkwave/Parser.h"
36 #include "libkwave/Sample.h"
37 #include "libkwave/String.h"
38 #include "libkwave/Writer.h"
39
40 #include "AsciiCodecPlugin.h"
41 #include "AsciiDecoder.h"
42
43 #define MAX_LINE_LEN 16384 /**< maximum line length in characters */
44
45 //***************************************************************************
AsciiDecoder()46 Kwave::AsciiDecoder::AsciiDecoder()
47 :Kwave::Decoder(),
48 m_source(),
49 m_dest(Q_NULLPTR),
50 m_queue_input(),
51 m_line_nr(0)
52 {
53 LOAD_MIME_TYPES
54 REGISTER_COMPRESSION_TYPES
55 m_source.setCodec("UTF-8");
56 }
57
58 //***************************************************************************
~AsciiDecoder()59 Kwave::AsciiDecoder::~AsciiDecoder()
60 {
61 if (m_source.device()) close();
62 }
63
64 //***************************************************************************
instance()65 Kwave::Decoder *Kwave::AsciiDecoder::instance()
66 {
67 return new(std::nothrow) Kwave::AsciiDecoder();
68 }
69
70 //***************************************************************************
open(QWidget * widget,QIODevice & src)71 bool Kwave::AsciiDecoder::open(QWidget *widget, QIODevice &src)
72 {
73 Q_UNUSED(widget)
74
75 metaData().clear();
76 Q_ASSERT(!m_source.device());
77 if (m_source.device()) qWarning("AsciiDecoder::open(), already open !");
78
79 // try to open the source
80 if (!src.open(QIODevice::ReadOnly)) {
81 qWarning("failed to open source !");
82 return false;
83 }
84
85 // take over the source
86 m_source.setDevice(&src);
87
88 Kwave::FileInfo info(metaData());
89 Kwave::LabelList labels;
90
91 /********** Decoder setup ************/
92 qDebug("--- AsciiDecoder::open() ---");
93
94 // read in all metadata until start of samples, EOF or user cancel
95 qDebug("AsciiDecoder::open(...)");
96
97 m_line_nr = 0;
98 while (!m_source.atEnd()) {
99 QString line = m_source.readLine(MAX_LINE_LEN).simplified();
100 m_line_nr++;
101 if (!line.length())
102 continue; // skip empty line
103
104 QRegExp regex(_(
105 "(^##\\s*)" // 1 start of meta data line
106 "([\\'\\\"])?" // 2 property, quote start (' or ")
107 "\\s*(\\w+[\\s\\w]*\\w)\\s*" // 3 property
108 "(\\[\\d*\\])?" // 4 index (optional)
109 "(\\2)" // 5 property, quote end
110 "(\\s*=\\s*)" // 6 assignment '='
111 "(.*)" // 7 rest, up to end of line
112 ));
113 if (regex.exactMatch(line)) {
114 // meta data entry: "## 'Name' = value"
115 QString name = Kwave::Parser::unescape(regex.cap(3) + regex.cap(4));
116 QString v = regex.cap(7);
117
118 QString value;
119 if (v.length()) {
120 // remove quotes from the value
121 bool is_escaped = false;
122 char quote = v[0].toLatin1();
123 if ((quote != '\'') && (quote != '"'))
124 quote = -1;
125
126 for (QString::ConstIterator it = v.constBegin();
127 it != v.constEnd(); ++it)
128 {
129 const char c = QChar(*it).toLatin1();
130
131 if ((c == '\\') && !is_escaped) {
132 is_escaped = true; // next char is escaped
133 continue;
134 }
135 if (is_escaped) {
136 value += *it; // escaped char
137 is_escaped = false;
138 continue;
139 }
140
141 if (c == quote) {
142 if (!value.length())
143 continue; // starting quote
144 else
145 break; // ending quote
146 }
147
148 if ((quote == -1) && (c == '#'))
149 break; // comment in unquoted text
150
151 // otherwise: normal character, part of text
152 value += *it;
153 }
154
155 // if the text was unquoted, remove leading/trailing spaces
156 if (quote == -1)
157 value = value.trimmed();
158 }
159
160 // handle some well known aliases
161 if (name == _("rate")) name = info.name(INF_SAMPLE_RATE);
162 if (name == _("bits")) name = info.name(INF_BITS_PER_SAMPLE);
163
164 // handle labels
165 QRegExp regex_label(_("label\\[(\\d*)\\]"));
166 if (regex_label.exactMatch(name)) {
167 bool ok = false;
168 sample_index_t pos = regex_label.cap(1).toULongLong(&ok);
169 if (!ok) {
170 qWarning("line %llu: malformed label position: '%s'",
171 m_line_nr, DBG(name));
172 continue; // skip it
173 }
174 Kwave::Label label(pos, value);
175 labels.append(label);
176 continue;
177 }
178
179 bool found = false;
180 foreach (const Kwave::FileProperty &p, info.allKnownProperties()) {
181 if (info.name(p).toLower() == name.toLower()) {
182 found = true;
183 info.set(p, QVariant(value));
184 }
185 }
186 if (!found) {
187 qWarning("line %llu: unknown meta data entry: '%s' = '%s'",
188 m_line_nr, DBG(name), DBG(value));
189 }
190 } else if (line.startsWith(QLatin1Char('#'))) {
191 continue; // skip comment lines
192 } else {
193 // reached end of metadata:
194 // -> push back the line into the queue
195 m_queue_input.enqueue(line);
196 break;
197 }
198 }
199
200 // if the number of channels is not known, but "tracks" is given and
201 // "track" is not present: old syntax has been used
202 if ((info.tracks() < 1) && info.contains(INF_TRACKS) &&
203 !info.contains(INF_TRACK))
204 {
205 info.set(INF_CHANNELS, info.get(INF_TRACKS));
206 info.set(INF_TRACKS, QVariant());
207 }
208
209 metaData().replace(Kwave::MetaDataList(info));
210 metaData().add(labels.toMetaDataList());
211
212 return (info.tracks() >= 1);
213 }
214
215 //***************************************************************************
readNextLine()216 bool Kwave::AsciiDecoder::readNextLine()
217 {
218 if (!m_queue_input.isEmpty())
219 return true; // there is still something in the queue
220
221 while (!m_source.atEnd()) {
222 QString line = m_source.readLine(MAX_LINE_LEN).simplified();
223 m_line_nr++;
224 if (!line.length()) {
225 continue; // skip empty line
226 } else if (line.startsWith(QLatin1Char('#'))) {
227 continue; // skip comment lines
228 } else {
229 // -> push back the line into the queue
230 m_queue_input.enqueue(line);
231 return true;
232 }
233 }
234 return false;
235 }
236
237 //***************************************************************************
decode(QWidget * widget,Kwave::MultiWriter & dst)238 bool Kwave::AsciiDecoder::decode(QWidget *widget,
239 Kwave::MultiWriter &dst)
240 {
241 Q_UNUSED(widget)
242
243 Q_ASSERT(m_source.device());
244 if (!m_source.device()) return false;
245
246 m_dest = &dst;
247
248 // for the moment: use a comma as separator <= TODO
249 const char separators[] = {',', '\0' };
250
251 Kwave::FileInfo info(metaData());
252 unsigned int channels = info.tracks();
253 QVector<sample_t> frame(channels);
254
255 // read in all remaining data until EOF or user cancel
256 qDebug("AsciiDecoder::decode(...)");
257 while (readNextLine() && !dst.isCanceled()) {
258 QByteArray d = m_queue_input.dequeue().toLatin1();
259 char *line = d.data();
260 char *saveptr = Q_NULLPTR;
261
262 frame.fill(0);
263 for (unsigned int channel = 0; channel < channels; channel++) {
264 sample_t s = 0;
265
266 char *token = strtok_r(line, separators, &saveptr);
267 line = Q_NULLPTR;
268 if (token) {
269 // skip whitespace at the start
270 while (*token && isspace(*token)) ++token;
271 if (*token) {
272 char *p = token + 1;
273 while (isdigit(*p) || (*p == '+') || (*p == '-')) ++p;
274 *p = 0;
275 if (*token) s = atoi(token);
276 Kwave::Writer *w = dst[channel];
277 if (w) (*w) << s;
278 }
279 }
280 }
281 }
282
283 m_dest = Q_NULLPTR;
284 info.setLength(dst.last() ? (dst.last() + 1) : 0);
285 metaData().replace(Kwave::MetaDataList(info));
286
287 // return with a valid Signal, even if the user pressed cancel !
288 return true;
289 }
290
291 //***************************************************************************
close()292 void Kwave::AsciiDecoder::close()
293 {
294 m_source.reset();
295 m_source.setDevice(Q_NULLPTR);
296 }
297
298 //***************************************************************************
299 //***************************************************************************
300