X-Git-Url: http://sigrok.org/gitweb/?a=blobdiff_plain;f=input%2Fcsv.c;h=fdfc26419ae7e44e910549dafc4f8067cc985d40;hb=HEAD;hp=2af4eeb158bf5066bb7da0a933eb5146cef1562c;hpb=56d0d24535700fb53e47a25ad5c73d34697695fa;p=libsigrok.git diff --git a/input/csv.c b/input/csv.c deleted file mode 100644 index 2af4eeb1..00000000 --- a/input/csv.c +++ /dev/null @@ -1,868 +0,0 @@ -/* - * This file is part of the libsigrok project. - * - * Copyright (C) 2013 Marc Schink - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include -#include -#include "libsigrok.h" -#include "libsigrok-internal.h" - -#define LOG_PREFIX "input/csv" - -/* - * The CSV input module has the following options: - * - * single-column: Specifies the column number which stores the sample data for - * single column mode and enables single column mode. Multi - * column mode is used if this parameter is omitted. - * - * numchannels: Specifies the number of channels to use. In multi column mode - * the number of channels are the number of columns and in single - * column mode the number of bits (LSB first) beginning at - * 'first-channel'. - * - * delimiter: Specifies the delimiter for columns. Must be at least one - * character. Comma is used as default delimiter. - * - * format: Specifies the format of the sample data in single column mode. - * Available formats are: 'bin', 'hex' and 'oct'. The binary - * format is used by default. This option has no effect in multi - * column mode. - * - * comment: Specifies the prefix character(s) for comments. No prefix - * characters are used by default which disables removing of - * comments. - * - * samplerate: Samplerate which the sample data was captured with. Default - * value is 0. - * - * first-channel: Column number of the first channel in multi column mode and - * position of the bit for the first channel in single column mode. - * Default value is 0. - * - * header: Determines if the first line should be treated as header - * and used for channel names in multi column mode. Empty header - * names will be replaced by the channel number. If enabled in - * single column mode the first line will be skipped. Usage of - * header is disabled by default. - * - * startline: Line number to start processing sample data. Must be greater - * than 0. The default line number to start processing is 1. - */ - -/* Single column formats. */ -enum { - FORMAT_BIN, - FORMAT_HEX, - FORMAT_OCT -}; - -struct context { - /* Current selected samplerate. */ - uint64_t samplerate; - - /* Number of channels. */ - gsize num_channels; - - /* Column delimiter character(s). */ - GString *delimiter; - - /* Comment prefix character(s). */ - GString *comment; - - /* Determines if sample data is stored in multiple columns. */ - gboolean multi_column_mode; - - /* Column number of the sample data in single column mode. */ - gsize single_column; - - /* - * Number of the first column to parse. Equivalent to the number of the - * first channel in multi column mode and the single column number in - * single column mode. - */ - gsize first_column; - - /* - * Column number of the first channel in multi column mode and position of - * the bit for the first channel in single column mode. - */ - gsize first_channel; - - /* Line number to start processing. */ - gsize start_line; - - /* - * Determines if the first line should be treated as header and used for - * channel names in multi column mode. - */ - gboolean header; - - /* Format sample data is stored in single column mode. */ - int format; - - /* Size of the sample buffer. */ - gsize sample_buffer_size; - - /* Buffer to store sample data. */ - uint8_t *sample_buffer; - - GIOChannel *channel; - - /* Buffer for the current line. */ - GString *buffer; - - /* Current line number. */ - gsize line_number; -}; - -static int format_match(const char *filename) -{ - if (!filename) { - sr_err("%s: filename was NULL.", __func__); - return FALSE; - } - - if (!g_file_test(filename, G_FILE_TEST_EXISTS)) { - sr_err("Input file '%s' does not exist.", filename); - return FALSE; - } - - if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) { - sr_err("Input file '%s' not a regular file.", filename); - return FALSE; - } - - return TRUE; -} - -static void free_context(struct context *ctx) -{ - if (!ctx) - return; - - if (ctx->delimiter) - g_string_free(ctx->delimiter, TRUE); - - if (ctx->comment) - g_string_free(ctx->comment, TRUE); - - if (ctx->channel) { - g_io_channel_shutdown(ctx->channel, FALSE, NULL); - g_io_channel_unref(ctx->channel); - } - - if (ctx->sample_buffer) - g_free(ctx->sample_buffer); - - if (ctx->buffer) - g_string_free(ctx->buffer, TRUE); - - g_free(ctx); -} - -static void strip_comment(GString *string, const GString *prefix) -{ - char *ptr; - - if (!prefix->len) - return; - - if (!(ptr = strstr(string->str, prefix->str))) - return; - - g_string_truncate(string, ptr - string->str); -} - -static int parse_binstr(const char *str, struct context *ctx) -{ - gsize i, j, length; - - length = strlen(str); - - if (!length) { - sr_err("Column %zu in line %zu is empty.", ctx->single_column, - ctx->line_number); - return SR_ERR; - } - - /* Clear buffer in order to set bits only. */ - memset(ctx->sample_buffer, 0, (ctx->num_channels + 7) >> 3); - - i = ctx->first_channel; - - for (j = 0; i < length && j < ctx->num_channels; i++, j++) { - if (str[length - i - 1] == '1') { - ctx->sample_buffer[j / 8] |= (1 << (j % 8)); - } else if (str[length - i - 1] != '0') { - sr_err("Invalid value '%s' in column %zu in line %zu.", - str, ctx->single_column, ctx->line_number); - return SR_ERR; - } - } - - return SR_OK; -} - -static int parse_hexstr(const char *str, struct context *ctx) -{ - gsize i, j, k, length; - uint8_t value; - char c; - - length = strlen(str); - - if (!length) { - sr_err("Column %zu in line %zu is empty.", ctx->single_column, - ctx->line_number); - return SR_ERR; - } - - /* Clear buffer in order to set bits only. */ - memset(ctx->sample_buffer, 0, (ctx->num_channels + 7) >> 3); - - /* Calculate the position of the first hexadecimal digit. */ - i = ctx->first_channel / 4; - - for (j = 0; i < length && j < ctx->num_channels; i++) { - c = str[length - i - 1]; - - if (!g_ascii_isxdigit(c)) { - sr_err("Invalid value '%s' in column %zu in line %zu.", - str, ctx->single_column, ctx->line_number); - return SR_ERR; - } - - value = g_ascii_xdigit_value(c); - - k = (ctx->first_channel + j) % 4; - - for (; j < ctx->num_channels && k < 4; k++) { - if (value & (1 << k)) - ctx->sample_buffer[j / 8] |= (1 << (j % 8)); - - j++; - } - } - - return SR_OK; -} - -static int parse_octstr(const char *str, struct context *ctx) -{ - gsize i, j, k, length; - uint8_t value; - char c; - - length = strlen(str); - - if (!length) { - sr_err("Column %zu in line %zu is empty.", ctx->single_column, - ctx->line_number); - return SR_ERR; - } - - /* Clear buffer in order to set bits only. */ - memset(ctx->sample_buffer, 0, (ctx->num_channels + 7) >> 3); - - /* Calculate the position of the first octal digit. */ - i = ctx->first_channel / 3; - - for (j = 0; i < length && j < ctx->num_channels; i++) { - c = str[length - i - 1]; - - if (c < '0' || c > '7') { - sr_err("Invalid value '%s' in column %zu in line %zu.", - str, ctx->single_column, ctx->line_number); - return SR_ERR; - } - - value = g_ascii_xdigit_value(c); - - k = (ctx->first_channel + j) % 3; - - for (; j < ctx->num_channels && k < 3; k++) { - if (value & (1 << k)) - ctx->sample_buffer[j / 8] |= (1 << (j % 8)); - - j++; - } - } - - return SR_OK; -} - -static char **parse_line(const struct context *ctx, int max_columns) -{ - const char *str, *remainder; - GSList *list, *l; - char **columns; - char *column; - gsize n, k; - - n = 0; - k = 0; - list = NULL; - - remainder = ctx->buffer->str; - str = strstr(remainder, ctx->delimiter->str); - - while (str && max_columns) { - if (n >= ctx->first_column) { - column = g_strndup(remainder, str - remainder); - list = g_slist_prepend(list, g_strstrip(column)); - - max_columns--; - k++; - } - - remainder = str + ctx->delimiter->len; - str = strstr(remainder, ctx->delimiter->str); - n++; - } - - if (ctx->buffer->len && max_columns && n >= ctx->first_column) { - column = g_strdup(remainder); - list = g_slist_prepend(list, g_strstrip(column)); - k++; - } - - if (!(columns = g_try_new(char *, k + 1))) - return NULL; - - columns[k--] = NULL; - - for (l = list; l; l = l->next) - columns[k--] = l->data; - - g_slist_free(list); - - return columns; -} - -static int parse_multi_columns(char **columns, struct context *ctx) -{ - gsize i; - - /* Clear buffer in order to set bits only. */ - memset(ctx->sample_buffer, 0, (ctx->num_channels + 7) >> 3); - - for (i = 0; i < ctx->num_channels; i++) { - if (columns[i][0] == '1') { - ctx->sample_buffer[i / 8] |= (1 << (i % 8)); - } else if (!strlen(columns[i])) { - sr_err("Column %zu in line %zu is empty.", - ctx->first_channel + i, ctx->line_number); - return SR_ERR; - } else if (columns[i][0] != '0') { - sr_err("Invalid value '%s' in column %zu in line %zu.", - columns[i], ctx->first_channel + i, - ctx->line_number); - return SR_ERR; - } - } - - return SR_OK; -} - -static int parse_single_column(const char *column, struct context *ctx) -{ - int res; - - res = SR_ERR; - - switch(ctx->format) { - case FORMAT_BIN: - res = parse_binstr(column, ctx); - break; - case FORMAT_HEX: - res = parse_hexstr(column, ctx); - break; - case FORMAT_OCT: - res = parse_octstr(column, ctx); - break; - } - - return res; -} - -static int send_samples(const struct sr_dev_inst *sdi, uint8_t *buffer, - gsize buffer_size, gsize count) -{ - int res; - struct sr_datafeed_packet packet; - struct sr_datafeed_logic logic; - gsize i; - - packet.type = SR_DF_LOGIC; - packet.payload = &logic; - logic.unitsize = buffer_size; - logic.length = buffer_size; - logic.data = buffer; - - for (i = 0; i < count; i++) { - if ((res = sr_session_send(sdi, &packet)) != SR_OK) - return res; - } - - return SR_OK; -} - -static int init(struct sr_input *in, const char *filename) -{ - int res; - struct context *ctx; - const char *param; - GIOStatus status; - gsize i, term_pos; - char channel_name[SR_MAX_PROBENAME_LEN + 1]; - struct sr_channel *ch; - char **columns; - gsize num_columns; - char *ptr; - - if (!(ctx = g_try_malloc0(sizeof(struct context)))) { - sr_err("Context malloc failed."); - return SR_ERR_MALLOC; - } - - /* Create a virtual device. */ - in->sdi = sr_dev_inst_new(0, SR_ST_ACTIVE, NULL, NULL, NULL); - in->internal = ctx; - - /* Set default samplerate. */ - ctx->samplerate = 0; - - /* - * Enable auto-detection of the number of channels in multi column mode - * and enforce the specification of the number of channels in single - * column mode. - */ - ctx->num_channels = 0; - - /* Set default delimiter. */ - if (!(ctx->delimiter = g_string_new(","))) { - sr_err("Delimiter malloc failed."); - free_context(ctx); - return SR_ERR_MALLOC; - } - - /* - * Set default comment prefix. Note that an empty comment prefix - * disables removing of comments. - */ - if (!(ctx->comment = g_string_new(""))) { - sr_err("Comment malloc failed."); - free_context(ctx); - return SR_ERR_MALLOC; - } - - /* Enable multi column mode by default. */ - ctx->multi_column_mode = TRUE; - - /* Use first column as default single column number. */ - ctx->single_column = 0; - - /* - * In multi column mode start parsing sample data at the first column - * and in single column mode at the first bit. - */ - ctx->first_channel = 0; - - /* Start at the beginning of the file. */ - ctx->start_line = 1; - - /* Disable the usage of the first line as header by default. */ - ctx->header = FALSE; - - /* Set default format for single column mode. */ - ctx->format = FORMAT_BIN; - - if (!(ctx->buffer = g_string_new(""))) { - sr_err("Line buffer malloc failed."); - free_context(ctx); - return SR_ERR_MALLOC; - } - - if (in->param) { - if ((param = g_hash_table_lookup(in->param, "samplerate"))) { - res = sr_parse_sizestring(param, &ctx->samplerate); - - if (res != SR_OK) { - sr_err("Invalid samplerate: %s.", param); - free_context(ctx); - return SR_ERR_ARG; - } - } - - if ((param = g_hash_table_lookup(in->param, "numchannels"))) - ctx->num_channels = g_ascii_strtoull(param, NULL, 10); - - if ((param = g_hash_table_lookup(in->param, "delimiter"))) { - if (!strlen(param)) { - sr_err("Delimiter must be at least one character."); - free_context(ctx); - return SR_ERR_ARG; - } - - if (!g_ascii_strcasecmp(param, "\\t")) - g_string_assign(ctx->delimiter, "\t"); - else - g_string_assign(ctx->delimiter, param); - } - - if ((param = g_hash_table_lookup(in->param, "comment"))) - g_string_assign(ctx->comment, param); - - if ((param = g_hash_table_lookup(in->param, "single-column"))) { - ctx->single_column = g_ascii_strtoull(param, &ptr, 10); - ctx->multi_column_mode = FALSE; - - if (param == ptr) { - sr_err("Invalid single-colum number: %s.", - param); - free_context(ctx); - return SR_ERR_ARG; - } - } - - if ((param = g_hash_table_lookup(in->param, "first-channel"))) - ctx->first_channel = g_ascii_strtoull(param, NULL, 10); - - if ((param = g_hash_table_lookup(in->param, "startline"))) { - ctx->start_line = g_ascii_strtoull(param, NULL, 10); - - if (ctx->start_line < 1) { - sr_err("Invalid start line: %s.", param); - free_context(ctx); - return SR_ERR_ARG; - } - } - - if ((param = g_hash_table_lookup(in->param, "header"))) - ctx->header = sr_parse_boolstring(param); - - if ((param = g_hash_table_lookup(in->param, "format"))) { - if (!g_ascii_strncasecmp(param, "bin", 3)) { - ctx->format = FORMAT_BIN; - } else if (!g_ascii_strncasecmp(param, "hex", 3)) { - ctx->format = FORMAT_HEX; - } else if (!g_ascii_strncasecmp(param, "oct", 3)) { - ctx->format = FORMAT_OCT; - } else { - sr_err("Invalid format: %s.", param); - free_context(ctx); - return SR_ERR; - } - } - } - - if (ctx->multi_column_mode) - ctx->first_column = ctx->first_channel; - else - ctx->first_column = ctx->single_column; - - if (!ctx->multi_column_mode && !ctx->num_channels) { - sr_err("Number of channels needs to be specified in single column mode."); - free_context(ctx); - return SR_ERR; - } - - if (!(ctx->channel = g_io_channel_new_file(filename, "r", NULL))) { - sr_err("Input file '%s' could not be opened.", filename); - free_context(ctx); - return SR_ERR; - } - - while (TRUE) { - ctx->line_number++; - status = g_io_channel_read_line_string(ctx->channel, - ctx->buffer, &term_pos, NULL); - - if (status == G_IO_STATUS_EOF) { - sr_err("Input file is empty."); - free_context(ctx); - return SR_ERR; - } - - if (status != G_IO_STATUS_NORMAL) { - sr_err("Error while reading line %zu.", - ctx->line_number); - free_context(ctx); - return SR_ERR; - } - - if (ctx->start_line > ctx->line_number) { - sr_spew("Line %zu skipped.", ctx->line_number); - continue; - } - - /* Remove line termination character(s). */ - g_string_truncate(ctx->buffer, term_pos); - - if (!ctx->buffer->len) { - sr_spew("Blank line %zu skipped.", ctx->line_number); - continue; - } - - /* Remove trailing comment. */ - strip_comment(ctx->buffer, ctx->comment); - - if (ctx->buffer->len) - break; - - sr_spew("Comment-only line %zu skipped.", ctx->line_number); - } - - /* - * In order to determine the number of columns parse the current line - * without limiting the number of columns. - */ - if (!(columns = parse_line(ctx, -1))) { - sr_err("Error while parsing line %zu.", ctx->line_number); - free_context(ctx); - return SR_ERR; - } - - num_columns = g_strv_length(columns); - - /* Ensure that the first column is not out of bounds. */ - if (!num_columns) { - sr_err("Column %zu in line %zu is out of bounds.", - ctx->first_column, ctx->line_number); - g_strfreev(columns); - free_context(ctx); - return SR_ERR; - } - - if (ctx->multi_column_mode) { - /* - * Detect the number of channels in multi column mode - * automatically if not specified. - */ - if (!ctx->num_channels) { - ctx->num_channels = num_columns; - sr_info("Number of auto-detected channels: %zu.", - ctx->num_channels); - } - - /* - * Ensure that the number of channels does not exceed the number - * of columns in multi column mode. - */ - if (num_columns < ctx->num_channels) { - sr_err("Not enough columns for desired number of channels in line %zu.", - ctx->line_number); - g_strfreev(columns); - free_context(ctx); - return SR_ERR; - } - } - - for (i = 0; i < ctx->num_channels; i++) { - if (ctx->header && ctx->multi_column_mode && strlen(columns[i])) - snprintf(channel_name, sizeof(channel_name), "%s", - columns[i]); - else - snprintf(channel_name, sizeof(channel_name), "%zu", i); - - ch = sr_channel_new(i, SR_PROBE_LOGIC, TRUE, channel_name); - - if (!ch) { - sr_err("Channel creation failed."); - free_context(ctx); - g_strfreev(columns); - return SR_ERR; - } - - in->sdi->channels = g_slist_append(in->sdi->channels, ch); - } - - g_strfreev(columns); - - /* - * Calculate the minimum buffer size to store the sample data of the - * channels. - */ - ctx->sample_buffer_size = (ctx->num_channels + 7) >> 3; - - if (!(ctx->sample_buffer = g_try_malloc(ctx->sample_buffer_size))) { - sr_err("Sample buffer malloc failed."); - free_context(ctx); - return SR_ERR_MALLOC; - } - - return SR_OK; -} - -static int loadfile(struct sr_input *in, const char *filename) -{ - int res; - struct context *ctx; - struct sr_datafeed_packet packet; - struct sr_datafeed_meta meta; - struct sr_config *cfg; - GIOStatus status; - gboolean read_new_line; - gsize term_pos; - char **columns; - gsize num_columns; - int max_columns; - - (void)filename; - - ctx = in->internal; - - /* Send header packet to the session bus. */ - std_session_send_df_header(in->sdi, LOG_PREFIX); - - if (ctx->samplerate) { - packet.type = SR_DF_META; - packet.payload = &meta; - cfg = sr_config_new(SR_CONF_SAMPLERATE, - g_variant_new_uint64(ctx->samplerate)); - meta.config = g_slist_append(NULL, cfg); - sr_session_send(in->sdi, &packet); - sr_config_free(cfg); - } - - read_new_line = FALSE; - - /* Limit the number of columns to parse. */ - if (ctx->multi_column_mode) - max_columns = ctx->num_channels; - else - max_columns = 1; - - while (TRUE) { - /* - * Skip reading a new line for the first time if the last read - * line was not a header because the sample data is not parsed - * yet. - */ - if (read_new_line || ctx->header) { - ctx->line_number++; - status = g_io_channel_read_line_string(ctx->channel, - ctx->buffer, &term_pos, NULL); - - if (status == G_IO_STATUS_EOF) - break; - - if (status != G_IO_STATUS_NORMAL) { - sr_err("Error while reading line %zu.", - ctx->line_number); - free_context(ctx); - return SR_ERR; - } - - /* Remove line termination character(s). */ - g_string_truncate(ctx->buffer, term_pos); - } - - read_new_line = TRUE; - - if (!ctx->buffer->len) { - sr_spew("Blank line %zu skipped.", ctx->line_number); - continue; - } - - /* Remove trailing comment. */ - strip_comment(ctx->buffer, ctx->comment); - - if (!ctx->buffer->len) { - sr_spew("Comment-only line %zu skipped.", - ctx->line_number); - continue; - } - - if (!(columns = parse_line(ctx, max_columns))) { - sr_err("Error while parsing line %zu.", - ctx->line_number); - free_context(ctx); - return SR_ERR; - } - - num_columns = g_strv_length(columns); - - /* Ensure that the first column is not out of bounds. */ - if (!num_columns) { - sr_err("Column %zu in line %zu is out of bounds.", - ctx->first_column, ctx->line_number); - g_strfreev(columns); - free_context(ctx); - return SR_ERR; - } - - /* - * Ensure that the number of channels does not exceed the number - * of columns in multi column mode. - */ - if (ctx->multi_column_mode && num_columns < ctx->num_channels) { - sr_err("Not enough columns for desired number of channels in line %zu.", - ctx->line_number); - g_strfreev(columns); - free_context(ctx); - return SR_ERR; - } - - if (ctx->multi_column_mode) - res = parse_multi_columns(columns, ctx); - else - res = parse_single_column(columns[0], ctx); - - if (res != SR_OK) { - g_strfreev(columns); - free_context(ctx); - return SR_ERR; - } - - g_strfreev(columns); - - /* - * TODO: Parse sample numbers / timestamps and use it for - * decompression. - */ - - /* Send sample data to the session bus. */ - res = send_samples(in->sdi, ctx->sample_buffer, - ctx->sample_buffer_size, 1); - - if (res != SR_OK) { - sr_err("Sending samples failed."); - free_context(ctx); - return SR_ERR; - } - } - - /* Send end packet to the session bus. */ - packet.type = SR_DF_END; - sr_session_send(in->sdi, &packet); - - free_context(ctx); - - return SR_OK; -} - -SR_PRIV struct sr_input_format input_csv = { - .id = "csv", - .description = "Comma-separated values (CSV)", - .format_match = format_match, - .init = init, - .loadfile = loadfile, -};