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