X-Git-Url: https://sigrok.org/gitweb/?a=blobdiff_plain;ds=inline;f=src%2Finput%2Fcsv.c;h=013d7a476b3338b6b14e993e8989b78019c371b7;hb=ccff468b5e1b841a0c4f0426f502b9dad23dd9be;hp=984a58c7d9753ae1d11869d829ad2e5507e9d686;hpb=3be42bc22f8b36599a448273c12a76d3e0f7a940;p=libsigrok.git diff --git a/src/input/csv.c b/src/input/csv.c index 984a58c7..013d7a47 100644 --- a/src/input/csv.c +++ b/src/input/csv.c @@ -67,6 +67,45 @@ * than 0. The default line number to start processing is 1. */ +/* + * TODO + * + * - Determine how the text line handling can get improved, regarding + * all of robustness and flexibility and correctness. + * - The current implementation splits on "any run of CR and LF". Which + * translates to: Line numbers are wrong in the presence of empty + * lines in the input stream. + * - The current implementation insists in the presence of end-of-line + * markers on _every_ line in the input stream. "Incomplete" text + * files that are so typical on the Windows platform get rejected as + * invalid. + * - Dropping support for CR style end-of-line markers could improve + * the situation a lot. Code could search for and split on LF, and + * trim optional trailing CR. This would result in proper support + * for CRLF (Windows) as well as LF (Unix), and allow for correct + * line number counts. + * - When support for CR-only line termination cannot get dropped, + * then the current implementation is inappropriate. Currently the + * input stream is scanned for the first occurance of either of the + * supported termination styles (which is good). For the remaining + * session a consistent encoding of the text lines is assumed (which + * is acceptable). Potential absence of the terminator for the last + * line is orthogonal, and can get handled by a "force" flag when + * the end() routine calls the process_buffer() routine. + * - When line numbers need to be correct and reliable, _and_ the full + * set of previously supported line termination sequences are required, + * and potentially more are to get added for improved compatibility + * with more platforms or generators, then the current approach of + * splitting on runs of termination characters needs to get replaced, + * by the more expensive approach to scan for and count the initially + * determined termination sequence. + * + * - Add support for analog input data? (optional) + * - Needs a syntax first for user specs which channels (columns) are + * logic and which are analog. May need heuristics(?) to guess from + * input data in the absence of user provided specs. + */ + /* Single column formats. */ enum { FORMAT_BIN, @@ -89,7 +128,7 @@ struct context { /* Comment prefix character(s). */ GString *comment; - /* Termination character(s) used in current stream. */ + /* Termination character(s) used in current stream. */ char *termination; /* Determines if sample data is stored in multiple columns. */ @@ -313,20 +352,22 @@ static char **parse_line(char *buf, struct context *inc, int max_columns) static int parse_multi_columns(char **columns, struct context *inc) { gsize i; + char *column; /* Clear buffer in order to set bits only. */ memset(inc->sample_buffer, 0, (inc->num_channels + 7) >> 3); for (i = 0; i < inc->num_channels; i++) { - if (columns[i][0] == '1') { + column = columns[i]; + if (column[0] == '1') { inc->sample_buffer[i / 8] |= (1 << (i % 8)); - } else if (!strlen(columns[i])) { + } else if (!strlen(column)) { sr_err("Column %zu in line %zu is empty.", inc->first_channel + i, inc->line_number); return SR_ERR; - } else if (columns[i][0] != '0') { + } else if (column[0] != '0') { sr_err("Invalid value '%s' in column %zu in line %zu.", - columns[i], inc->first_channel + i, + column, inc->first_channel + i, inc->line_number); return SR_ERR; } @@ -371,7 +412,8 @@ static int send_samples(const struct sr_dev_inst *sdi, uint8_t *buffer, logic.data = buffer; for (i = 0; i < count; i++) { - if ((res = sr_session_send(sdi, &packet)) != SR_OK) + res = sr_session_send(sdi, &packet); + if (res != SR_OK) return res; } @@ -444,6 +486,8 @@ static int init(struct sr_input *in, GHashTable *options) return SR_OK; } +static const char *delim_set = "\r\n"; + static const char *get_line_termination(GString *buf) { const char *term; @@ -466,26 +510,27 @@ static int initial_parse(const struct sr_input *in, GString *buf) unsigned int num_columns, i; size_t line_number, l; int ret; - char **lines, **columns; + char **lines, *line, **columns, *column; ret = SR_OK; inc = in->priv; columns = NULL; line_number = 0; - lines = g_strsplit_set(buf->str, "\r\n", 0); + lines = g_strsplit_set(buf->str, delim_set, 0); for (l = 0; lines[l]; l++) { line_number++; + line = lines[l]; if (inc->start_line > line_number) { sr_spew("Line %zu skipped.", line_number); continue; } - if (lines[l][0] == '\0') { + if (line[0] == '\0') { sr_spew("Blank line %zu skipped.", line_number); continue; } - strip_comment(lines[l], inc->comment); - if (lines[l][0] == '\0') { + strip_comment(line, inc->comment); + if (line[0] == '\0') { sr_spew("Comment-only line %zu skipped.", line_number); continue; } @@ -503,7 +548,8 @@ static int initial_parse(const struct sr_input *in, GString *buf) * In order to determine the number of columns parse the current line * without limiting the number of columns. */ - if (!(columns = parse_line(lines[l], inc, -1))) { + columns = parse_line(line, inc, -1); + if (!columns) { sr_err("Error while parsing line %zu.", line_number); ret = SR_ERR; goto out; @@ -543,8 +589,9 @@ static int initial_parse(const struct sr_input *in, GString *buf) channel_name = g_string_sized_new(64); for (i = 0; i < inc->num_channels; i++) { - if (inc->header && inc->multi_column_mode && columns[i][0] != '\0') - g_string_assign(channel_name, columns[i]); + column = columns[i]; + if (inc->header && inc->multi_column_mode && column[0] != '\0') + g_string_assign(channel_name, column); else g_string_printf(channel_name, "%u", i); sr_channel_new(in->sdi, i, SR_CHANNEL_LOGIC, TRUE, channel_name->str); @@ -576,11 +623,13 @@ static int initial_receive(const struct sr_input *in) inc = in->priv; - if (!(termination = get_line_termination(in->buf))) + termination = get_line_termination(in->buf); + if (!termination) /* Don't have a full line yet. */ return SR_ERR_NA; - if (!(p = g_strrstr_len(in->buf->str, in->buf->len, termination))) + p = g_strrstr_len(in->buf->str, in->buf->len, termination); + if (!p) /* Don't have a full line yet. */ return SR_ERR_NA; len = p - in->buf->str - 1; @@ -608,11 +657,11 @@ static int process_buffer(struct sr_input *in) gsize num_columns; uint64_t samplerate; int max_columns, ret, l; - char *p, **lines, **columns; + char *p, **lines, *line, **columns; inc = in->priv; if (!inc->started) { - std_session_send_df_header(in->sdi, LOG_PREFIX); + std_session_send_df_header(in->sdi); if (inc->samplerate) { packet.type = SR_DF_META; @@ -628,31 +677,41 @@ static int process_buffer(struct sr_input *in) inc->started = TRUE; } - if (!(p = g_strrstr_len(in->buf->str, in->buf->len, inc->termination))) - /* Don't have a full line. */ - return SR_ERR; - - *p = '\0'; - g_strstrip(in->buf->str); - /* Limit the number of columns to parse. */ if (inc->multi_column_mode) max_columns = inc->num_channels; else max_columns = 1; + /* + * Consider empty input non-fatal. Keep accumulating input until + * at least one full text line has become available. Grab the + * maximum amount of accumulated data that consists of full text + * lines, and process what has been received so far, leaving not + * yet complete lines for the next invocation. + */ + if (!in->buf->len) + return SR_OK; + p = g_strrstr_len(in->buf->str, in->buf->len, inc->termination); + if (!p) + return SR_ERR; + *p = '\0'; + p += strlen(inc->termination); + g_strstrip(in->buf->str); + ret = SR_OK; - lines = g_strsplit_set(in->buf->str, "\r\n", 0); + lines = g_strsplit_set(in->buf->str, delim_set, 0); for (l = 0; lines[l]; l++) { inc->line_number++; - if (lines[l][0] == '\0') { + line = lines[l]; + if (line[0] == '\0') { sr_spew("Blank line %zu skipped.", inc->line_number); continue; } /* Remove trailing comment. */ - strip_comment(lines[l], inc->comment); - if (lines[l][0] == '\0') { + strip_comment(line, inc->comment); + if (line[0] == '\0') { sr_spew("Comment-only line %zu skipped.", inc->line_number); continue; } @@ -664,7 +723,8 @@ static int process_buffer(struct sr_input *in) continue; } - if (!(columns = parse_line(lines[l], inc, max_columns))) { + columns = parse_line(line, inc, max_columns); + if (!columns) { sr_err("Error while parsing line %zu.", inc->line_number); return SR_ERR; } @@ -705,7 +765,7 @@ static int process_buffer(struct sr_input *in) g_strfreev(columns); } g_strfreev(lines); - g_string_erase(in->buf, 0, p - in->buf->str + 1); + g_string_erase(in->buf, 0, p - in->buf->str); return ret; } @@ -719,7 +779,8 @@ static int receive(struct sr_input *in, GString *buf) inc = in->priv; if (!inc->termination) { - if ((ret = initial_receive(in)) == SR_ERR_NA) + ret = initial_receive(in); + if (ret == SR_ERR_NA) /* Not enough data yet. */ return SR_OK; else if (ret != SR_OK) @@ -747,7 +808,7 @@ static int end(struct sr_input *in) inc = in->priv; if (inc->started) - std_session_send_df_end(in->sdi, LOG_PREFIX); + std_session_send_df_end(in->sdi); return ret; } @@ -768,6 +829,17 @@ static void cleanup(struct sr_input *in) g_free(inc->sample_buffer); } +static int reset(struct sr_input *in) +{ + struct context *inc = in->priv; + + cleanup(in); + inc->started = FALSE; + g_string_truncate(in->buf, 0); + + return SR_OK; +} + static struct sr_option options[] = { { "single-column", "Single column", "Enable/specify single column", NULL, NULL }, { "numchannels", "Max channels", "Number of channels", NULL, NULL }, @@ -808,4 +880,5 @@ SR_PRIV struct sr_input_module input_csv = { .receive = receive, .end = end, .cleanup = cleanup, + .reset = reset, };