annotate data/fileio/CSVFormat.cpp @ 1870:1b8c4ee06f6d csv-import-headers

Detect presence of header row in CSV format guesser; use headings to inform our guesses about column purposes; test this
author Chris Cannam
date Wed, 17 Jun 2020 18:01:00 +0100
parents bde22957545e
children bed42ce4d3ab
rev   line source
Chris@392 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@392 2
Chris@392 3 /*
Chris@392 4 Sonic Visualiser
Chris@392 5 An audio file viewer and annotation editor.
Chris@392 6 Centre for Digital Music, Queen Mary, University of London.
Chris@392 7 This file copyright 2006 Chris Cannam.
Chris@392 8
Chris@392 9 This program is free software; you can redistribute it and/or
Chris@392 10 modify it under the terms of the GNU General Public License as
Chris@392 11 published by the Free Software Foundation; either version 2 of the
Chris@392 12 License, or (at your option) any later version. See the file
Chris@392 13 COPYING included with this distribution for more information.
Chris@392 14 */
Chris@392 15
Chris@392 16 #include "CSVFormat.h"
Chris@392 17
Chris@629 18 #include "base/StringBits.h"
Chris@629 19
Chris@392 20 #include <QFile>
Chris@392 21 #include <QString>
Chris@392 22 #include <QRegExp>
Chris@392 23 #include <QStringList>
Chris@392 24 #include <QTextStream>
Chris@392 25
Chris@392 26 #include <iostream>
Chris@392 27
Chris@1362 28 #include "base/Debug.h"
Chris@1362 29
Chris@629 30 CSVFormat::CSVFormat(QString path) :
Chris@629 31 m_separator(""),
Chris@392 32 m_sampleRate(44100),
Chris@392 33 m_windowSize(1024),
Chris@1870 34 m_headerStatus(HeaderUnknown),
Chris@1870 35 m_allowQuoting(true),
Chris@1870 36 m_maxExampleCols(0)
Chris@392 37 {
Chris@1524 38 (void)guessFormatFor(path);
Chris@629 39 }
Chris@629 40
Chris@1524 41 bool
Chris@629 42 CSVFormat::guessFormatFor(QString path)
Chris@629 43 {
Chris@629 44 m_modelType = TwoDimensionalModel;
Chris@629 45 m_timingType = ExplicitTiming;
Chris@629 46 m_timeUnits = TimeSeconds;
Chris@629 47
Chris@629 48 m_maxExampleCols = 0;
Chris@629 49 m_columnCount = 0;
Chris@629 50 m_variableColumnCount = false;
Chris@629 51
Chris@629 52 m_example.clear();
Chris@629 53 m_columnQualities.clear();
Chris@629 54 m_columnPurposes.clear();
Chris@629 55 m_prevValues.clear();
Chris@629 56
Chris@629 57 QFile file(path);
Chris@1524 58 if (!file.exists()) {
Chris@1524 59 SVCERR << "CSVFormat::guessFormatFor(" << path
Chris@1524 60 << "): File does not exist" << endl;
Chris@1524 61 return false;
Chris@1524 62 }
Chris@1524 63 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
Chris@1524 64 SVCERR << "CSVFormat::guessFormatFor(" << path
Chris@1524 65 << "): File could not be opened for reading" << endl;
Chris@1524 66 return false;
Chris@1524 67 }
Chris@1524 68 SVDEBUG << "CSVFormat::guessFormatFor(" << path << ")" << endl;
Chris@392 69
Chris@392 70 QTextStream in(&file);
Chris@392 71 in.seek(0);
Chris@392 72
Chris@629 73 int lineno = 0;
Chris@392 74
Chris@392 75 while (!in.atEnd()) {
Chris@392 76
Chris@392 77 // See comment about line endings in CSVFileReader::load()
Chris@392 78
Chris@392 79 QString chunk = in.readLine();
Chris@392 80 QStringList lines = chunk.split('\r', QString::SkipEmptyParts);
Chris@392 81
Chris@897 82 for (int li = 0; li < lines.size(); ++li) {
Chris@392 83
Chris@392 84 QString line = lines[li];
Chris@1512 85 if (line.startsWith("#") || line == "") {
Chris@1512 86 continue;
Chris@1512 87 }
Chris@392 88
Chris@629 89 guessQualities(line, lineno);
Chris@392 90
Chris@840 91 ++lineno;
Chris@629 92 }
Chris@840 93
Chris@1512 94 if (lineno >= 150) break;
Chris@629 95 }
Chris@392 96
Chris@629 97 guessPurposes();
Chris@1515 98 guessAudioSampleRange();
Chris@1524 99
Chris@1524 100 return true;
Chris@629 101 }
Chris@629 102
Chris@629 103 void
Chris@629 104 CSVFormat::guessSeparator(QString line)
Chris@629 105 {
Chris@1524 106 QString candidates = "\t|,/: ";
Chris@1524 107
Chris@1524 108 for (int i = 0; i < candidates.length(); ++i) {
Chris@1524 109 auto bits = StringBits::split(line, candidates[i], m_allowQuoting);
Chris@1524 110 if (bits.size() >= 2) {
Chris@1585 111 m_plausibleSeparators.insert(candidates[i]);
Chris@1585 112 if (m_separator == "") {
Chris@1585 113 m_separator = candidates[i];
Chris@1585 114 SVDEBUG << "Estimated column separator: '" << m_separator
Chris@1585 115 << "'" << endl;
Chris@1524 116 }
Chris@629 117 }
Chris@629 118 }
Chris@629 119 }
Chris@629 120
Chris@629 121 void
Chris@629 122 CSVFormat::guessQualities(QString line, int lineno)
Chris@629 123 {
Chris@1585 124 guessSeparator(line);
Chris@629 125
Chris@1362 126 QStringList list = StringBits::split(line, getSeparator(), m_allowQuoting);
Chris@629 127
Chris@629 128 int cols = list.size();
Chris@1870 129
Chris@1870 130 int firstLine = 0;
Chris@1870 131 if (m_headerStatus == HeaderPresent) {
Chris@1870 132 firstLine = 1;
Chris@1870 133 }
Chris@1870 134
Chris@1870 135 if (lineno == firstLine || (cols > m_columnCount)) {
Chris@1870 136 m_columnCount = cols;
Chris@1870 137 }
Chris@1870 138 if (cols != m_columnCount) {
Chris@1870 139 m_variableColumnCount = true;
Chris@1870 140 }
Chris@629 141
Chris@629 142 // All columns are regarded as having these qualities until we see
Chris@629 143 // something that indicates otherwise:
Chris@629 144
Chris@629 145 ColumnQualities defaultQualities =
Chris@1512 146 ColumnNumeric | ColumnIntegral | ColumnSmall |
Chris@1512 147 ColumnIncreasing | ColumnNearEmpty;
Chris@629 148
Chris@629 149 for (int i = 0; i < cols; ++i) {
Chris@1854 150
Chris@1854 151 SVDEBUG << "line no " << lineno << ": column " << i << " contains: \"" << list[i] << "\"" << endl;
Chris@1870 152
Chris@1870 153 if (m_columnQualities.find(i) == m_columnQualities.end()) {
Chris@1870 154 m_columnQualities[i] = defaultQualities;
Chris@1870 155 m_prevValues[i] = 0.f;
Chris@629 156 }
Chris@629 157
Chris@629 158 QString s(list[i]);
Chris@629 159 bool ok = false;
Chris@629 160
Chris@629 161 ColumnQualities qualities = m_columnQualities[i];
Chris@629 162
Chris@1523 163 // Looks like this is defined on Windows
Chris@1523 164 #undef small
Chris@1523 165
Chris@629 166 bool numeric = (qualities & ColumnNumeric);
Chris@629 167 bool integral = (qualities & ColumnIntegral);
Chris@629 168 bool increasing = (qualities & ColumnIncreasing);
Chris@1512 169 bool small = (qualities & ColumnSmall);
Chris@629 170 bool large = (qualities & ColumnLarge); // this one defaults to off
Chris@1512 171 bool signd = (qualities & ColumnSigned); // also defaults to off
Chris@1021 172 bool emptyish = (qualities & ColumnNearEmpty);
Chris@629 173
Chris@1854 174 if (s.trimmed() != "") {
Chris@1021 175
Chris@1870 176 if (lineno > firstLine) {
Chris@1854 177 emptyish = false;
Chris@1854 178 }
Chris@1854 179
Chris@1854 180 float value = 0.f;
Chris@629 181
Chris@1854 182 if (numeric) {
Chris@1854 183 value = s.toFloat(&ok);
Chris@1854 184 if (!ok) {
Chris@1854 185 value = (float)StringBits::stringToDoubleLocaleFree(s, &ok);
Chris@1512 186 }
Chris@1854 187 if (ok) {
Chris@1870 188 if (lineno < firstLine + 2 && value > 1000.f) {
Chris@1854 189 large = true;
Chris@1854 190 }
Chris@1854 191 if (value < 0.f) {
Chris@1854 192 signd = true;
Chris@1854 193 }
Chris@1854 194 if (value < -1.f || value > 1.f) {
Chris@1854 195 small = false;
Chris@1854 196 }
Chris@1854 197 } else {
Chris@1854 198 numeric = false;
Chris@1854 199
Chris@1854 200 // If the column is not numeric, it can't be any of
Chris@1854 201 // these things either
Chris@1854 202 integral = false;
Chris@1854 203 increasing = false;
Chris@1512 204 small = false;
Chris@1854 205 large = false;
Chris@1854 206 signd = false;
Chris@392 207 }
Chris@392 208 }
Chris@392 209
Chris@1854 210 if (numeric) {
Chris@1854 211
Chris@1854 212 if (integral) {
Chris@1854 213 if (s.contains('.') || s.contains(',')) {
Chris@1854 214 integral = false;
Chris@1854 215 }
Chris@392 216 }
Chris@1854 217
Chris@1854 218 if (increasing) {
Chris@1870 219 if (lineno > firstLine && value <= m_prevValues[i]) {
Chris@1854 220 increasing = false;
Chris@1854 221 }
Chris@1854 222 }
Chris@1854 223
Chris@1854 224 m_prevValues[i] = value;
Chris@392 225 }
Chris@629 226 }
Chris@1524 227
Chris@629 228 m_columnQualities[i] =
Chris@629 229 (numeric ? ColumnNumeric : 0) |
Chris@629 230 (integral ? ColumnIntegral : 0) |
Chris@629 231 (increasing ? ColumnIncreasing : 0) |
Chris@1512 232 (small ? ColumnSmall : 0) |
Chris@1021 233 (large ? ColumnLarge : 0) |
Chris@1512 234 (signd ? ColumnSigned : 0) |
Chris@1021 235 (emptyish ? ColumnNearEmpty : 0);
Chris@629 236 }
Chris@392 237
Chris@1870 238 if (lineno == 0 && m_headerStatus == HeaderUnknown) {
Chris@1870 239 // If we have at least one column, and every column has
Chris@1870 240 // quality == ColumnNearEmpty, i.e. not empty and not numeric,
Chris@1870 241 // then we probably have a header row
Chris@1870 242 bool couldBeHeader = (cols > 0);
Chris@1870 243 std::map<int, QString> headings;
Chris@1870 244 for (int i = 0; i < cols; ++i) {
Chris@1870 245 if (m_columnQualities[i] != ColumnNearEmpty) {
Chris@1870 246 couldBeHeader = false;
Chris@1870 247 } else {
Chris@1870 248 headings[i] = list[i].trimmed().toLower();
Chris@1870 249 }
Chris@1870 250 }
Chris@1870 251 if (couldBeHeader) {
Chris@1870 252 m_headerStatus = HeaderPresent;
Chris@1870 253 m_columnHeadings = headings;
Chris@1870 254 } else {
Chris@1870 255 m_headerStatus = HeaderAbsent;
Chris@1870 256 }
Chris@1870 257 }
Chris@1870 258
Chris@1870 259 if (lineno == 0 && m_headerStatus == HeaderPresent) {
Chris@1870 260 // Start again with the qualities:
Chris@1870 261 m_columnQualities.clear();
Chris@1870 262 m_prevValues.clear();
Chris@1870 263 } else if (lineno < firstLine + 10) {
Chris@1870 264 // Not a header row, so add it to the example column output
Chris@629 265 m_example.push_back(list);
Chris@1870 266 if (lineno == firstLine || cols > m_maxExampleCols) {
Chris@629 267 m_maxExampleCols = cols;
Chris@392 268 }
Chris@392 269 }
Chris@392 270
Chris@1870 271 if (lineno < firstLine + 10) {
Chris@1362 272 SVDEBUG << "Estimated column qualities for line " << lineno << " (reporting up to first 10): ";
Chris@1870 273 if (lineno == 0 && m_headerStatus == HeaderPresent &&
Chris@1870 274 m_columnCount > 0 && m_columnQualities.empty()) {
Chris@1870 275 SVDEBUG << "[whole line classified as a header row]";
Chris@1870 276 } else {
Chris@1870 277 for (int i = 0; i < cols; ++i) {
Chris@1870 278 if (m_columnQualities.find(i) == m_columnQualities.end()) {
Chris@1870 279 SVDEBUG << "(not set) ";
Chris@1870 280 } else {
Chris@1870 281 SVDEBUG << int(m_columnQualities[i]) << " ";
Chris@1870 282 }
Chris@1870 283 }
Chris@1362 284 }
Chris@1362 285 SVDEBUG << endl;
Chris@1870 286 SVDEBUG << "Estimated header status: " << m_headerStatus << endl;
Chris@1362 287 }
Chris@629 288 }
Chris@629 289
Chris@629 290 void
Chris@629 291 CSVFormat::guessPurposes()
Chris@629 292 {
Chris@629 293 m_timingType = CSVFormat::ImplicitTiming;
Chris@629 294 m_timeUnits = CSVFormat::TimeWindows;
Chris@1429 295
Chris@629 296 int timingColumnCount = 0;
Chris@1525 297 bool haveDurationOrEndTime = false;
Chris@1021 298
Chris@1510 299 SVDEBUG << "Estimated column qualities overall: ";
Chris@1510 300 for (int i = 0; i < m_columnCount; ++i) {
Chris@1870 301 if (m_columnQualities.find(i) == m_columnQualities.end()) {
Chris@1870 302 SVDEBUG << "(not set) ";
Chris@1870 303 } else {
Chris@1870 304 SVDEBUG << int(m_columnQualities[i]) << " ";
Chris@1870 305 }
Chris@1510 306 }
Chris@1510 307 SVDEBUG << endl;
Chris@1510 308
Chris@1021 309 // if our first column has zero or one entries in it and the rest
Chris@1021 310 // have more, then we'll default to ignoring the first column and
Chris@1021 311 // counting the next one as primary. (e.g. Sonic Annotator output
Chris@1021 312 // with filename at start of first column.)
Chris@1021 313
Chris@1021 314 int primaryColumnNo = 0;
Chris@1021 315
Chris@1021 316 if (m_columnCount >= 2) {
Chris@1021 317 if ( (m_columnQualities[0] & ColumnNearEmpty) &&
Chris@1021 318 !(m_columnQualities[1] & ColumnNearEmpty)) {
Chris@1021 319 primaryColumnNo = 1;
Chris@1021 320 }
Chris@1021 321 }
Chris@629 322
Chris@629 323 for (int i = 0; i < m_columnCount; ++i) {
Chris@629 324
Chris@629 325 ColumnPurpose purpose = ColumnUnknown;
Chris@1021 326
Chris@1021 327 if (i < primaryColumnNo) {
Chris@1021 328 setColumnPurpose(i, purpose);
Chris@1021 329 continue;
Chris@1021 330 }
Chris@1021 331
Chris@1021 332 bool primary = (i == primaryColumnNo);
Chris@392 333
Chris@629 334 ColumnQualities qualities = m_columnQualities[i];
Chris@392 335
Chris@629 336 bool numeric = (qualities & ColumnNumeric);
Chris@629 337 bool integral = (qualities & ColumnIntegral);
Chris@629 338 bool increasing = (qualities & ColumnIncreasing);
Chris@629 339 bool large = (qualities & ColumnLarge);
Chris@629 340
Chris@629 341 bool timingColumn = (numeric && increasing);
Chris@629 342
Chris@1870 343 QString heading;
Chris@1870 344 if (m_columnHeadings.find(i) != m_columnHeadings.end()) {
Chris@1870 345 heading = m_columnHeadings[i];
Chris@1870 346 }
Chris@1870 347
Chris@1870 348 if (heading == "time" || heading == "frame" ||
Chris@1870 349 heading == "duration" || heading == "endtime") {
Chris@1870 350 timingColumn = true;
Chris@1870 351 }
Chris@1870 352
Chris@1870 353 if (heading == "value" || heading == "height" || heading == "label") {
Chris@1870 354 timingColumn = false;
Chris@1870 355 }
Chris@1870 356
Chris@629 357 if (timingColumn) {
Chris@629 358
Chris@629 359 ++timingColumnCount;
Chris@1870 360
Chris@1870 361 if (heading == "endtime") {
Chris@1870 362
Chris@1870 363 purpose = ColumnEndTime;
Chris@1870 364 haveDurationOrEndTime = true;
Chris@1870 365
Chris@1870 366 } else if (heading == "duration") {
Chris@1870 367
Chris@1870 368 purpose = ColumnDuration;
Chris@1870 369 haveDurationOrEndTime = true;
Chris@629 370
Chris@1870 371 } else if (primary || heading == "time" || heading == "frame") {
Chris@629 372
Chris@629 373 purpose = ColumnStartTime;
Chris@629 374 m_timingType = ExplicitTiming;
Chris@629 375
Chris@1870 376 if ((integral && large) || heading == "frame") {
Chris@629 377 m_timeUnits = TimeAudioFrames;
Chris@629 378 } else {
Chris@629 379 m_timeUnits = TimeSeconds;
Chris@629 380 }
Chris@629 381
Chris@1870 382 } else if (timingColumnCount == 2 &&
Chris@1870 383 m_timingType == ExplicitTiming) {
Chris@1870 384 purpose = ColumnEndTime;
Chris@1870 385 haveDurationOrEndTime = true;
Chris@629 386 }
Chris@629 387 }
Chris@629 388
Chris@629 389 if (purpose == ColumnUnknown) {
Chris@1870 390 if (heading == "label") {
Chris@1870 391 purpose = ColumnLabel;
Chris@1870 392 } else if (numeric || heading == "value" || heading == "height") {
Chris@629 393 purpose = ColumnValue;
Chris@629 394 } else {
Chris@629 395 purpose = ColumnLabel;
Chris@629 396 }
Chris@629 397 }
Chris@629 398
Chris@631 399 setColumnPurpose(i, purpose);
Chris@629 400 }
Chris@629 401
Chris@629 402 int valueCount = 0;
Chris@629 403 for (int i = 0; i < m_columnCount; ++i) {
Chris@1870 404 if (m_columnPurposes[i] == ColumnValue) {
Chris@1870 405 ++valueCount;
Chris@1870 406 }
Chris@629 407 }
Chris@629 408
Chris@630 409 if (valueCount == 2 && timingColumnCount == 1) {
Chris@630 410 // If we have exactly two apparent value columns and only one
Chris@630 411 // timing column, but one value column is integral and the
Chris@630 412 // other is not, guess that whichever one matches the integral
Chris@630 413 // status of the time column is either duration or end time
Chris@630 414 if (m_timingType == ExplicitTiming) {
Chris@630 415 int a = -1, b = -1;
Chris@630 416 for (int i = 0; i < m_columnCount; ++i) {
Chris@630 417 if (m_columnPurposes[i] == ColumnValue) {
Chris@630 418 if (a == -1) a = i;
Chris@630 419 else b = i;
Chris@630 420 }
Chris@630 421 }
Chris@630 422 if ((m_columnQualities[a] & ColumnIntegral) !=
Chris@630 423 (m_columnQualities[b] & ColumnIntegral)) {
Chris@630 424 int timecol = a;
Chris@630 425 if ((m_columnQualities[a] & ColumnIntegral) !=
Chris@630 426 (m_columnQualities[0] & ColumnIntegral)) {
Chris@630 427 timecol = b;
Chris@630 428 }
Chris@630 429 if (m_columnQualities[timecol] & ColumnIncreasing) {
Chris@630 430 // This shouldn't happen; should have been settled above
Chris@630 431 m_columnPurposes[timecol] = ColumnEndTime;
Chris@1525 432 haveDurationOrEndTime = true;
Chris@630 433 } else {
Chris@630 434 m_columnPurposes[timecol] = ColumnDuration;
Chris@1525 435 haveDurationOrEndTime = true;
Chris@630 436 }
Chris@630 437 --valueCount;
Chris@630 438 }
Chris@630 439 }
Chris@630 440 }
Chris@630 441
Chris@1525 442 if (timingColumnCount > 1 || haveDurationOrEndTime) {
Chris@631 443 m_modelType = TwoDimensionalModelWithDuration;
Chris@392 444 } else {
Chris@631 445 if (valueCount == 0) {
Chris@631 446 m_modelType = OneDimensionalModel;
Chris@631 447 } else if (valueCount == 1) {
Chris@631 448 m_modelType = TwoDimensionalModel;
Chris@631 449 } else {
Chris@631 450 m_modelType = ThreeDimensionalModel;
Chris@631 451 }
Chris@629 452 }
Chris@392 453
Chris@1362 454 SVDEBUG << "Estimated column purposes: ";
Chris@1362 455 for (int i = 0; i < m_columnCount; ++i) {
Chris@1362 456 SVDEBUG << int(m_columnPurposes[i]) << " ";
Chris@1362 457 }
Chris@1362 458 SVDEBUG << endl;
Chris@392 459
Chris@1362 460 SVDEBUG << "Estimated model type: " << m_modelType << endl;
Chris@1362 461 SVDEBUG << "Estimated timing type: " << m_timingType << endl;
Chris@1362 462 SVDEBUG << "Estimated units: " << m_timeUnits << endl;
Chris@392 463 }
Chris@392 464
Chris@1515 465 void
Chris@1515 466 CSVFormat::guessAudioSampleRange()
Chris@1515 467 {
Chris@1515 468 AudioSampleRange range = SampleRangeSigned1;
Chris@1515 469
Chris@1515 470 range = SampleRangeSigned1;
Chris@1515 471 bool knownSigned = false;
Chris@1515 472 bool knownNonIntegral = false;
Chris@1521 473
Chris@1521 474 SVDEBUG << "CSVFormat::guessAudioSampleRange: starting with assumption of "
Chris@1521 475 << range << endl;
Chris@1515 476
Chris@1515 477 for (int i = 0; i < m_columnCount; ++i) {
Chris@1521 478 if (m_columnPurposes[i] != ColumnValue) {
Chris@1521 479 SVDEBUG << "... column " << i
Chris@1521 480 << " is not apparently a value, ignoring" << endl;
Chris@1521 481 continue;
Chris@1521 482 }
Chris@1515 483 if (!(m_columnQualities[i] & ColumnIntegral)) {
Chris@1515 484 knownNonIntegral = true;
Chris@1515 485 if (range == SampleRangeUnsigned255 ||
Chris@1515 486 range == SampleRangeSigned32767) {
Chris@1515 487 range = SampleRangeOther;
Chris@1515 488 }
Chris@1521 489 SVDEBUG << "... column " << i
Chris@1521 490 << " is non-integral, updating range to " << range << endl;
Chris@1515 491 }
Chris@1515 492 if (m_columnQualities[i] & ColumnLarge) {
Chris@1515 493 if (range == SampleRangeSigned1 ||
Chris@1515 494 range == SampleRangeUnsigned255) {
Chris@1515 495 if (knownNonIntegral) {
Chris@1515 496 range = SampleRangeOther;
Chris@1515 497 } else {
Chris@1515 498 range = SampleRangeSigned32767;
Chris@1515 499 }
Chris@1515 500 }
Chris@1521 501 SVDEBUG << "... column " << i << " is large, updating range to "
Chris@1521 502 << range << endl;
Chris@1515 503 }
Chris@1515 504 if (m_columnQualities[i] & ColumnSigned) {
Chris@1515 505 knownSigned = true;
Chris@1515 506 if (range == SampleRangeUnsigned255) {
Chris@1515 507 range = SampleRangeSigned32767;
Chris@1515 508 }
Chris@1521 509 SVDEBUG << "... column " << i << " is signed, updating range to "
Chris@1521 510 << range << endl;
Chris@1515 511 }
Chris@1515 512 if (!(m_columnQualities[i] & ColumnSmall)) {
Chris@1515 513 if (range == SampleRangeSigned1) {
Chris@1515 514 if (knownNonIntegral) {
Chris@1515 515 range = SampleRangeOther;
Chris@1515 516 } else if (knownSigned) {
Chris@1515 517 range = SampleRangeSigned32767;
Chris@1515 518 } else {
Chris@1515 519 range = SampleRangeUnsigned255;
Chris@1515 520 }
Chris@1515 521 }
Chris@1521 522 SVDEBUG << "... column " << i << " is not small, updating range to "
Chris@1521 523 << range << endl;
Chris@1515 524 }
Chris@1515 525 }
Chris@1515 526
Chris@1521 527 SVDEBUG << "CSVFormat::guessAudioSampleRange: ended up with range "
Chris@1521 528 << range << endl;
Chris@1521 529
Chris@1515 530 m_audioSampleRange = range;
Chris@1515 531 }
Chris@1515 532
Chris@1870 533 QList<CSVFormat::ColumnPurpose>
Chris@1870 534 CSVFormat::getColumnPurposes() const
Chris@631 535 {
Chris@1870 536 QList<ColumnPurpose> purposes;
Chris@1870 537 for (int i = 0; i < m_columnCount; ++i) {
Chris@1870 538 purposes.push_back(getColumnPurpose(i));
Chris@631 539 }
Chris@1870 540 return purposes;
Chris@1870 541 }
Chris@1870 542
Chris@1870 543 void
Chris@1870 544 CSVFormat::setColumnPurposes(QList<ColumnPurpose> cl)
Chris@1870 545 {
Chris@1870 546 m_columnPurposes.clear();
Chris@1870 547 for (int i = 0; in_range_for(cl, i); ++i) {
Chris@1870 548 m_columnPurposes[i] = cl[i];
Chris@1870 549 }
Chris@631 550 }
Chris@629 551
Chris@631 552 CSVFormat::ColumnPurpose
Chris@631 553 CSVFormat::getColumnPurpose(int i) const
Chris@631 554 {
Chris@1870 555 if (m_columnPurposes.find(i) == m_columnPurposes.end()) {
Chris@668 556 return ColumnUnknown;
Chris@1870 557 } else {
Chris@1870 558 return m_columnPurposes.at(i);
Chris@668 559 }
Chris@631 560 }
Chris@631 561
Chris@631 562 void
Chris@631 563 CSVFormat::setColumnPurpose(int i, ColumnPurpose p)
Chris@631 564 {
Chris@631 565 m_columnPurposes[i] = p;
Chris@631 566 }
Chris@631 567
Chris@1870 568 QList<CSVFormat::ColumnQualities>
Chris@1870 569 CSVFormat::getColumnQualities() const
Chris@1870 570 {
Chris@1870 571 QList<ColumnQualities> qualities;
Chris@1870 572 for (int i = 0; i < m_columnCount; ++i) {
Chris@1870 573 if (m_columnQualities.find(i) == m_columnQualities.end()) {
Chris@1870 574 qualities.push_back(0);
Chris@1870 575 } else {
Chris@1870 576 qualities.push_back(m_columnQualities.at(i));
Chris@1870 577 }
Chris@1870 578 }
Chris@1870 579 return qualities;
Chris@1870 580 }