|
|
@@ -0,0 +1,581 @@
|
|
|
+/*
|
|
|
+libcsv - parse and write csv data
|
|
|
+Copyright (C) 2008 Robert Gamble
|
|
|
+
|
|
|
+This library is free software; you can redistribute it and/or
|
|
|
+modify it under the terms of the GNU Lesser General Public
|
|
|
+License as published by the Free Software Foundation; either
|
|
|
+version 2.1 of the License, or (at your option) any later version.
|
|
|
+
|
|
|
+This library 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
|
|
|
+Lesser General Public License for more details.
|
|
|
+
|
|
|
+You should have received a copy of the GNU Lesser General Public
|
|
|
+License along with this library; if not, write to the Free Software
|
|
|
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
+*/
|
|
|
+
|
|
|
+#if ___STDC_VERSION__ >= 199901L
|
|
|
+# include <stdint.h>
|
|
|
+#else
|
|
|
+# define SIZE_MAX ((size_t)-1) /* C89 doesn't have stdint.h or SIZE_MAX */
|
|
|
+#endif
|
|
|
+
|
|
|
+#include "csv.h"
|
|
|
+
|
|
|
+#define VERSION "3.0.3"
|
|
|
+
|
|
|
+#define ROW_NOT_BEGUN 0
|
|
|
+#define FIELD_NOT_BEGUN 1
|
|
|
+#define FIELD_BEGUN 2
|
|
|
+#define FIELD_MIGHT_HAVE_ENDED 3
|
|
|
+
|
|
|
+/*
|
|
|
+ Explanation of states
|
|
|
+ ROW_NOT_BEGUN There have not been any fields encountered for this row
|
|
|
+ FIELD_NOT_BEGUN There have been fields but we are currently not in one
|
|
|
+ FIELD_BEGUN We are in a field
|
|
|
+ FIELD_MIGHT_HAVE_ENDED
|
|
|
+ We encountered a double quote inside a quoted field, the
|
|
|
+ field is either ended or the quote is literal
|
|
|
+*/
|
|
|
+
|
|
|
+#define MEM_BLK_SIZE 128
|
|
|
+
|
|
|
+#define SUBMIT_FIELD(p) \
|
|
|
+ do { \
|
|
|
+ if (!quoted) \
|
|
|
+ entry_pos -= spaces; \
|
|
|
+ if (p->options & CSV_APPEND_NULL) \
|
|
|
+ ((p)->entry_buf[entry_pos]) = '\0'; \
|
|
|
+ if (cb1 && (p->options & CSV_EMPTY_IS_NULL) && !quoted && entry_pos == 0) \
|
|
|
+ cb1(NULL, entry_pos, data); \
|
|
|
+ else if (cb1) \
|
|
|
+ cb1(p->entry_buf, entry_pos, data); \
|
|
|
+ pstate = FIELD_NOT_BEGUN; \
|
|
|
+ entry_pos = quoted = spaces = 0; \
|
|
|
+ } while (0)
|
|
|
+
|
|
|
+#define SUBMIT_ROW(p, c) \
|
|
|
+ do { \
|
|
|
+ if (cb2) \
|
|
|
+ cb2(c, data); \
|
|
|
+ pstate = ROW_NOT_BEGUN; \
|
|
|
+ entry_pos = quoted = spaces = 0; \
|
|
|
+ } while (0)
|
|
|
+
|
|
|
+#define SUBMIT_CHAR(p, c) ((p)->entry_buf[entry_pos++] = (c))
|
|
|
+
|
|
|
+static char *csv_errors[] = {"success",
|
|
|
+ "error parsing data while strict checking enabled",
|
|
|
+ "memory exhausted while increasing buffer size",
|
|
|
+ "data size too large",
|
|
|
+ "invalid status code"};
|
|
|
+
|
|
|
+int
|
|
|
+csv_error(struct csv_parser *p)
|
|
|
+{
|
|
|
+ /* Return the current status of the parser */
|
|
|
+ return p->status;
|
|
|
+}
|
|
|
+
|
|
|
+char *
|
|
|
+csv_strerror(int status)
|
|
|
+{
|
|
|
+ /* Return a textual description of status */
|
|
|
+ if (status >= CSV_EINVALID || status < 0)
|
|
|
+ return csv_errors[CSV_EINVALID];
|
|
|
+ else
|
|
|
+ return csv_errors[status];
|
|
|
+}
|
|
|
+
|
|
|
+int
|
|
|
+csv_get_opts(struct csv_parser *p)
|
|
|
+{
|
|
|
+ /* Return the currently set options of parser */
|
|
|
+ if (p == NULL)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ return p->options;
|
|
|
+}
|
|
|
+
|
|
|
+int
|
|
|
+csv_set_opts(struct csv_parser *p, unsigned char options)
|
|
|
+{
|
|
|
+ /* Set the options */
|
|
|
+ if (p == NULL)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ p->options = options;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int
|
|
|
+csv_init(struct csv_parser *p, unsigned char options)
|
|
|
+{
|
|
|
+ /* Initialize a csv_parser object returns 0 on success, -1 on error */
|
|
|
+ if (p == NULL)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ p->entry_buf = NULL;
|
|
|
+ p->pstate = ROW_NOT_BEGUN;
|
|
|
+ p->quoted = 0;
|
|
|
+ p->spaces = 0;
|
|
|
+ p->entry_pos = 0;
|
|
|
+ p->entry_size = 0;
|
|
|
+ p->status = 0;
|
|
|
+ p->options = options;
|
|
|
+ p->quote_char = CSV_QUOTE;
|
|
|
+ p->delim_char = CSV_COMMA;
|
|
|
+ p->is_space = NULL;
|
|
|
+ p->is_term = NULL;
|
|
|
+ p->blk_size = MEM_BLK_SIZE;
|
|
|
+ p->malloc_func = NULL;
|
|
|
+ p->realloc_func = realloc;
|
|
|
+ p->free_func = free;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+void
|
|
|
+csv_free(struct csv_parser *p)
|
|
|
+{
|
|
|
+ /* Free the entry_buffer of csv_parser object */
|
|
|
+ if (p == NULL)
|
|
|
+ return;
|
|
|
+
|
|
|
+ if (p->entry_buf)
|
|
|
+ p->free_func(p->entry_buf);
|
|
|
+
|
|
|
+ p->entry_buf = NULL;
|
|
|
+ p->entry_size = 0;
|
|
|
+
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
+int
|
|
|
+csv_fini(struct csv_parser *p, void (*cb1)(void *, size_t, void *), void (*cb2)(int c, void *), void *data)
|
|
|
+{
|
|
|
+ /* Finalize parsing. Needed, for example, when file does not end in a newline */
|
|
|
+ int quoted = p->quoted;
|
|
|
+ int pstate = p->pstate;
|
|
|
+ size_t spaces = p->spaces;
|
|
|
+ size_t entry_pos = p->entry_pos;
|
|
|
+
|
|
|
+ if (p == NULL)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+
|
|
|
+ if (p->pstate == FIELD_BEGUN && p->quoted && p->options & CSV_STRICT && p->options & CSV_STRICT_FINI) {
|
|
|
+ /* Current field is quoted, no end-quote was seen, and CSV_STRICT_FINI is set */
|
|
|
+ p->status = CSV_EPARSE;
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (p->pstate) {
|
|
|
+ case FIELD_MIGHT_HAVE_ENDED:
|
|
|
+ p->entry_pos -= p->spaces + 1; /* get rid of spaces and original quote */
|
|
|
+ /* Fall-through */
|
|
|
+ case FIELD_NOT_BEGUN:
|
|
|
+ case FIELD_BEGUN:
|
|
|
+ quoted = p->quoted, pstate = p->pstate;
|
|
|
+ spaces = p->spaces, entry_pos = p->entry_pos;
|
|
|
+ SUBMIT_FIELD(p);
|
|
|
+ SUBMIT_ROW(p, -1);
|
|
|
+ case ROW_NOT_BEGUN: /* Already ended properly */
|
|
|
+ ;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Reset parser */
|
|
|
+ p->spaces = p->quoted = p->entry_pos = p->status = 0;
|
|
|
+ p->pstate = ROW_NOT_BEGUN;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+void
|
|
|
+csv_set_delim(struct csv_parser *p, unsigned char c)
|
|
|
+{
|
|
|
+ /* Set the delimiter */
|
|
|
+ if (p) p->delim_char = c;
|
|
|
+}
|
|
|
+
|
|
|
+void
|
|
|
+csv_set_quote(struct csv_parser *p, unsigned char c)
|
|
|
+{
|
|
|
+ /* Set the quote character */
|
|
|
+ if (p) p->quote_char = c;
|
|
|
+}
|
|
|
+
|
|
|
+unsigned char
|
|
|
+csv_get_delim(struct csv_parser *p)
|
|
|
+{
|
|
|
+ /* Get the delimiter */
|
|
|
+ return p->delim_char;
|
|
|
+}
|
|
|
+
|
|
|
+unsigned char
|
|
|
+csv_get_quote(struct csv_parser *p)
|
|
|
+{
|
|
|
+ /* Get the quote character */
|
|
|
+ return p->quote_char;
|
|
|
+}
|
|
|
+
|
|
|
+void
|
|
|
+csv_set_space_func(struct csv_parser *p, int (*f)(unsigned char))
|
|
|
+{
|
|
|
+ /* Set the space function */
|
|
|
+ if (p) p->is_space = f;
|
|
|
+}
|
|
|
+
|
|
|
+void
|
|
|
+csv_set_term_func(struct csv_parser *p, int (*f)(unsigned char))
|
|
|
+{
|
|
|
+ /* Set the term function */
|
|
|
+ if (p) p->is_term = f;
|
|
|
+}
|
|
|
+
|
|
|
+void
|
|
|
+csv_set_realloc_func(struct csv_parser *p, void *(*f)(void *, size_t))
|
|
|
+{
|
|
|
+ /* Set the realloc function used to increase buffer size */
|
|
|
+ if (p && f) p->realloc_func = f;
|
|
|
+}
|
|
|
+
|
|
|
+void
|
|
|
+csv_set_free_func(struct csv_parser *p, void (*f)(void *))
|
|
|
+{
|
|
|
+ /* Set the free function used to free the buffer */
|
|
|
+ if (p && f) p->free_func = f;
|
|
|
+}
|
|
|
+
|
|
|
+void
|
|
|
+csv_set_blk_size(struct csv_parser *p, size_t size)
|
|
|
+{
|
|
|
+ /* Set the block size used to increment buffer size */
|
|
|
+ if (p) p->blk_size = size;
|
|
|
+}
|
|
|
+
|
|
|
+size_t
|
|
|
+csv_get_buffer_size(struct csv_parser *p)
|
|
|
+{
|
|
|
+ /* Get the size of the entry buffer */
|
|
|
+ if (p)
|
|
|
+ return p->entry_size;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int
|
|
|
+csv_increase_buffer(struct csv_parser *p)
|
|
|
+{
|
|
|
+ /* Increase the size of the entry buffer. Attempt to increase size by
|
|
|
+ * p->blk_size, if this is larger than SIZE_MAX try to increase current
|
|
|
+ * buffer size to SIZE_MAX. If allocation fails, try to allocate halve
|
|
|
+ * the size and try again until successful or increment size is zero.
|
|
|
+ */
|
|
|
+
|
|
|
+ size_t to_add = p->blk_size;
|
|
|
+ void *vp;
|
|
|
+
|
|
|
+ if ( p->entry_size >= SIZE_MAX - to_add )
|
|
|
+ to_add = SIZE_MAX - p->entry_size;
|
|
|
+
|
|
|
+ if (!to_add) {
|
|
|
+ p->status = CSV_ETOOBIG;
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ while ((vp = p->realloc_func(p->entry_buf, p->entry_size + to_add)) == NULL) {
|
|
|
+ to_add /= 2;
|
|
|
+ if (!to_add) {
|
|
|
+ p->status = CSV_ENOMEM;
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Update entry buffer pointer and entry_size if successful */
|
|
|
+ p->entry_buf = vp;
|
|
|
+ p->entry_size += to_add;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+size_t
|
|
|
+csv_parse(struct csv_parser *p, const void *s, size_t len, void (*cb1)(void *, size_t, void *), void (*cb2)(int c, void *), void *data)
|
|
|
+{
|
|
|
+ unsigned const char *us = s; /* Access input data as array of unsigned char */
|
|
|
+ unsigned char c; /* The character we are currently processing */
|
|
|
+ size_t pos = 0; /* The number of characters we have processed in this call */
|
|
|
+
|
|
|
+ /* Store key fields into local variables for performance */
|
|
|
+ unsigned char delim = p->delim_char;
|
|
|
+ unsigned char quote = p->quote_char;
|
|
|
+ int (*is_space)(unsigned char) = p->is_space;
|
|
|
+ int (*is_term)(unsigned char) = p->is_term;
|
|
|
+ int quoted = p->quoted;
|
|
|
+ int pstate = p->pstate;
|
|
|
+ size_t spaces = p->spaces;
|
|
|
+ size_t entry_pos = p->entry_pos;
|
|
|
+
|
|
|
+
|
|
|
+ if (!p->entry_buf && pos < len) {
|
|
|
+ /* Buffer hasn't been allocated yet and len > 0 */
|
|
|
+ if (csv_increase_buffer(p) != 0) {
|
|
|
+ p->quoted = quoted, p->pstate = pstate, p->spaces = spaces, p->entry_pos = entry_pos;
|
|
|
+ return pos;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ while (pos < len) {
|
|
|
+ /* Check memory usage, increase buffer if neccessary */
|
|
|
+ if (entry_pos == ((p->options & CSV_APPEND_NULL) ? p->entry_size - 1 : p->entry_size) ) {
|
|
|
+ if (csv_increase_buffer(p) != 0) {
|
|
|
+ p->quoted = quoted, p->pstate = pstate, p->spaces = spaces, p->entry_pos = entry_pos;
|
|
|
+ return pos;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ c = us[pos++];
|
|
|
+
|
|
|
+ switch (pstate) {
|
|
|
+ case ROW_NOT_BEGUN:
|
|
|
+ case FIELD_NOT_BEGUN:
|
|
|
+ if ((is_space ? is_space(c) : c == CSV_SPACE || c == CSV_TAB) && c!=delim) { /* Space or Tab */
|
|
|
+ continue;
|
|
|
+ } else if (is_term ? is_term(c) : c == CSV_CR || c == CSV_LF) { /* Carriage Return or Line Feed */
|
|
|
+ if (pstate == FIELD_NOT_BEGUN) {
|
|
|
+ SUBMIT_FIELD(p);
|
|
|
+ SUBMIT_ROW(p, (unsigned char)c);
|
|
|
+ } else { /* ROW_NOT_BEGUN */
|
|
|
+ /* Don't submit empty rows by default */
|
|
|
+ if (p->options & CSV_REPALL_NL) {
|
|
|
+ SUBMIT_ROW(p, (unsigned char)c);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ continue;
|
|
|
+ } else if (c == delim) { /* Comma */
|
|
|
+ SUBMIT_FIELD(p);
|
|
|
+ break;
|
|
|
+ } else if (c == quote) { /* Quote */
|
|
|
+ pstate = FIELD_BEGUN;
|
|
|
+ quoted = 1;
|
|
|
+ } else { /* Anything else */
|
|
|
+ pstate = FIELD_BEGUN;
|
|
|
+ quoted = 0;
|
|
|
+ SUBMIT_CHAR(p, c);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case FIELD_BEGUN:
|
|
|
+ if (c == quote) { /* Quote */
|
|
|
+ if (quoted) {
|
|
|
+ SUBMIT_CHAR(p, c);
|
|
|
+ pstate = FIELD_MIGHT_HAVE_ENDED;
|
|
|
+ } else {
|
|
|
+ /* STRICT ERROR - double quote inside non-quoted field */
|
|
|
+ if (p->options & CSV_STRICT) {
|
|
|
+ p->status = CSV_EPARSE;
|
|
|
+ p->quoted = quoted, p->pstate = pstate, p->spaces = spaces, p->entry_pos = entry_pos;
|
|
|
+ return pos-1;
|
|
|
+ }
|
|
|
+ SUBMIT_CHAR(p, c);
|
|
|
+ spaces = 0;
|
|
|
+ }
|
|
|
+ } else if (c == delim) { /* Comma */
|
|
|
+ if (quoted) {
|
|
|
+ SUBMIT_CHAR(p, c);
|
|
|
+ } else {
|
|
|
+ SUBMIT_FIELD(p);
|
|
|
+ }
|
|
|
+ } else if (is_term ? is_term(c) : c == CSV_CR || c == CSV_LF) { /* Carriage Return or Line Feed */
|
|
|
+ if (!quoted) {
|
|
|
+ SUBMIT_FIELD(p);
|
|
|
+ SUBMIT_ROW(p, (unsigned char)c);
|
|
|
+ } else {
|
|
|
+ SUBMIT_CHAR(p, c);
|
|
|
+ }
|
|
|
+ } else if (!quoted && (is_space? is_space(c) : c == CSV_SPACE || c == CSV_TAB)) { /* Tab or space for non-quoted field */
|
|
|
+ SUBMIT_CHAR(p, c);
|
|
|
+ spaces++;
|
|
|
+ } else { /* Anything else */
|
|
|
+ SUBMIT_CHAR(p, c);
|
|
|
+ spaces = 0;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case FIELD_MIGHT_HAVE_ENDED:
|
|
|
+ /* This only happens when a quote character is encountered in a quoted field */
|
|
|
+ if (c == delim) { /* Comma */
|
|
|
+ entry_pos -= spaces + 1; /* get rid of spaces and original quote */
|
|
|
+ SUBMIT_FIELD(p);
|
|
|
+ } else if (is_term ? is_term(c) : c == CSV_CR || c == CSV_LF) { /* Carriage Return or Line Feed */
|
|
|
+ entry_pos -= spaces + 1; /* get rid of spaces and original quote */
|
|
|
+ SUBMIT_FIELD(p);
|
|
|
+ SUBMIT_ROW(p, (unsigned char)c);
|
|
|
+ } else if (is_space ? is_space(c) : c == CSV_SPACE || c == CSV_TAB) { /* Space or Tab */
|
|
|
+ SUBMIT_CHAR(p, c);
|
|
|
+ spaces++;
|
|
|
+ } else if (c == quote) { /* Quote */
|
|
|
+ if (spaces) {
|
|
|
+ /* STRICT ERROR - unescaped double quote */
|
|
|
+ if (p->options & CSV_STRICT) {
|
|
|
+ p->status = CSV_EPARSE;
|
|
|
+ p->quoted = quoted, p->pstate = pstate, p->spaces = spaces, p->entry_pos = entry_pos;
|
|
|
+ return pos-1;
|
|
|
+ }
|
|
|
+ spaces = 0;
|
|
|
+ SUBMIT_CHAR(p, c);
|
|
|
+ } else {
|
|
|
+ /* Two quotes in a row */
|
|
|
+ pstate = FIELD_BEGUN;
|
|
|
+ }
|
|
|
+ } else { /* Anything else */
|
|
|
+ /* STRICT ERROR - unescaped double quote */
|
|
|
+ if (p->options & CSV_STRICT) {
|
|
|
+ p->status = CSV_EPARSE;
|
|
|
+ p->quoted = quoted, p->pstate = pstate, p->spaces = spaces, p->entry_pos = entry_pos;
|
|
|
+ return pos-1;
|
|
|
+ }
|
|
|
+ pstate = FIELD_BEGUN;
|
|
|
+ spaces = 0;
|
|
|
+ SUBMIT_CHAR(p, c);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ p->quoted = quoted, p->pstate = pstate, p->spaces = spaces, p->entry_pos = entry_pos;
|
|
|
+ return pos;
|
|
|
+}
|
|
|
+
|
|
|
+size_t
|
|
|
+csv_write (void *dest, size_t dest_size, const void *src, size_t src_size)
|
|
|
+{
|
|
|
+ unsigned char *cdest = dest;
|
|
|
+ const unsigned char *csrc = src;
|
|
|
+ size_t chars = 0;
|
|
|
+
|
|
|
+ if (src == NULL)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ if (cdest == NULL)
|
|
|
+ dest_size = 0;
|
|
|
+
|
|
|
+ if (dest_size > 0)
|
|
|
+ *cdest++ = '"';
|
|
|
+ chars++;
|
|
|
+
|
|
|
+ while (src_size) {
|
|
|
+ if (*csrc == '"') {
|
|
|
+ if (dest_size > chars)
|
|
|
+ *cdest++ = '"';
|
|
|
+ if (chars < SIZE_MAX) chars++;
|
|
|
+ }
|
|
|
+ if (dest_size > chars)
|
|
|
+ *cdest++ = *csrc;
|
|
|
+ if (chars < SIZE_MAX) chars++;
|
|
|
+ src_size--;
|
|
|
+ csrc++;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (dest_size > chars)
|
|
|
+ *cdest = '"';
|
|
|
+ if (chars < SIZE_MAX) chars++;
|
|
|
+
|
|
|
+ return chars;
|
|
|
+}
|
|
|
+
|
|
|
+int
|
|
|
+csv_fwrite (FILE *fp, const void *src, size_t src_size)
|
|
|
+{
|
|
|
+ const unsigned char *csrc = src;
|
|
|
+
|
|
|
+ if (fp == NULL || src == NULL)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ if (fputc('"', fp) == EOF)
|
|
|
+ return EOF;
|
|
|
+
|
|
|
+ while (src_size) {
|
|
|
+ if (*csrc == '"') {
|
|
|
+ if (fputc('"', fp) == EOF)
|
|
|
+ return EOF;
|
|
|
+ }
|
|
|
+ if (fputc(*csrc, fp) == EOF)
|
|
|
+ return EOF;
|
|
|
+ src_size--;
|
|
|
+ csrc++;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (fputc('"', fp) == EOF) {
|
|
|
+ return EOF;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+size_t
|
|
|
+csv_write2 (void *dest, size_t dest_size, const void *src, size_t src_size, unsigned char quote)
|
|
|
+{
|
|
|
+ unsigned char *cdest = dest;
|
|
|
+ const unsigned char *csrc = src;
|
|
|
+ size_t chars = 0;
|
|
|
+
|
|
|
+ if (src == NULL)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ if (dest == NULL)
|
|
|
+ dest_size = 0;
|
|
|
+
|
|
|
+ if (dest_size > 0)
|
|
|
+ *cdest++ = quote;
|
|
|
+ chars++;
|
|
|
+
|
|
|
+ while (src_size) {
|
|
|
+ if (*csrc == quote) {
|
|
|
+ if (dest_size > chars)
|
|
|
+ *cdest++ = quote;
|
|
|
+ if (chars < SIZE_MAX) chars++;
|
|
|
+ }
|
|
|
+ if (dest_size > chars)
|
|
|
+ *cdest++ = *csrc;
|
|
|
+ if (chars < SIZE_MAX) chars++;
|
|
|
+ src_size--;
|
|
|
+ csrc++;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (dest_size > chars)
|
|
|
+ *cdest = quote;
|
|
|
+ if (chars < SIZE_MAX) chars++;
|
|
|
+
|
|
|
+ return chars;
|
|
|
+}
|
|
|
+
|
|
|
+int
|
|
|
+csv_fwrite2 (FILE *fp, const void *src, size_t src_size, unsigned char quote)
|
|
|
+{
|
|
|
+ const unsigned char *csrc = src;
|
|
|
+
|
|
|
+ if (fp == NULL || src == NULL)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ if (fputc(quote, fp) == EOF)
|
|
|
+ return EOF;
|
|
|
+
|
|
|
+ while (src_size) {
|
|
|
+ if (*csrc == quote) {
|
|
|
+ if (fputc(quote, fp) == EOF)
|
|
|
+ return EOF;
|
|
|
+ }
|
|
|
+ if (fputc(*csrc, fp) == EOF)
|
|
|
+ return EOF;
|
|
|
+ src_size--;
|
|
|
+ csrc++;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (fputc(quote, fp) == EOF) {
|
|
|
+ return EOF;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|