rsstats 0.0.1
Redis Enterprise Statistic collector
csv.c
Go to the documentation of this file.
1
21#ifdef HAVE_CONFIG_H
22#include "config.h"
23#endif
24
25#include "csv.h"
26
27#include <stdlib.h>
28#include <string.h>
29
30static unsigned short int _csv_firstfield = 1;
31
32void csv_addline(FILE* reportfile) {
33 fprintf(reportfile,"\r\n");
34 _csv_firstfield = 1;
35}
36
37void csv_addfield(FILE* reportfile, const char* value) {
38 char* csv = txt2csv(value);
39 if (_csv_firstfield) {
40 fprintf(reportfile,"%s",csv);
41 _csv_firstfield = 0;
42 } else
43 fprintf(reportfile,",%s",csv);
44 free(csv);
45}
46
47/* RFC4180 compliant CSV parser, with LF only tolerance when CRLF expected */
48/* https://www.rfc-editor.org/rfc/rfc4180 */
49static char* _csvtok_csv=NULL;
50char* csvtok(char* source) {
51 size_t csv_idx=0;
52 size_t txt_idx=0;
53 char* csvtok_txt = NULL;
54 unsigned short int quoted = 0;
55
56 if (NULL!=source)
57 _csvtok_csv = source;
58
59 if (_csvtok_csv[0]==0)
60 return NULL;
61
62 if (NULL==(csvtok_txt = malloc(strlen(_csvtok_csv)+1))) {
63 perror("csvtok csvtok_txt malloc");
64 exit(EXIT_FAILURE);
65 }
66 csvtok_txt[0]=0;
67
68 while (_csvtok_csv[csv_idx]) {
69 if (!quoted) {
70 if (_csvtok_csv[csv_idx]==',') {
71 if ((_csvtok_csv[csv_idx+1]==0)||
72 (_csvtok_csv[csv_idx+1]=='\n')|| /* Unix style tolerance */
73 ((_csvtok_csv[csv_idx+1]=='\r')&&(_csvtok_csv[csv_idx+2]=='\n'))) {
74 fprintf(stderr,"RFC4180 forbids comma at the end of record %s at %zu.\n",
75 _csvtok_csv,csv_idx);
76 free(csvtok_txt);
77 _csvtok_csv=NULL;
78 return NULL;
79 } else
80 break;
81 } else if ((_csvtok_csv[csv_idx]=='\n')||
82 ((_csvtok_csv[csv_idx]=='\r')&&(_csvtok_csv[csv_idx+1]=='\n')))
83 break;
84 else if (_csvtok_csv[csv_idx]=='"') {
85 if (csv_idx==0) {
86 quoted = 1;
87 csv_idx++;
88 continue;
89 } else {
90 fprintf(stderr,"doublequote in a non quoted value %s at %zu.\n",
91 _csvtok_csv,csv_idx);
92 free(csvtok_txt);
93 _csvtok_csv=NULL;
94 return NULL;
95 }
96 }
97 } else { /* Quoted */
98 if (_csvtok_csv[csv_idx]=='"') {
99 if (_csvtok_csv[csv_idx+1]=='"') {
100 /* Skip escaping doublequote and let the copy occur */
101 csv_idx++;
102 } else if ((_csvtok_csv[csv_idx+1]=='0')||
103 (_csvtok_csv[csv_idx+1]==',')||
104 (_csvtok_csv[csv_idx+1]=='\n')||
105 ((_csvtok_csv[csv_idx+1]=='\r')&&(_csvtok_csv[csv_idx+2]=='\n'))) {
106 quoted = 0;
107 csv_idx++;
108 break;
109 } else {
110 fprintf(stderr,"doublequote should be at the end of field or escaping another doublequote in %s at %zu.\n",
111 _csvtok_csv,csv_idx);
112 free(csvtok_txt);
113 _csvtok_csv=NULL;
114 return NULL;
115 }
116 }
117 }
118 csvtok_txt[txt_idx++]=_csvtok_csv[csv_idx++];
119 }
120
121 /* Close csvtok_txt */
122 csvtok_txt[txt_idx]=0;
123 if (quoted) {
124 fprintf(stderr,"Missing end-of-field doublequote %s\n",csvtok_txt);
125 free(csvtok_txt);
126 _csvtok_csv=NULL;
127 return NULL;
128 } else if (_csvtok_csv[csv_idx]==',') /* Next char after end of field should be 0/,/CRLF */
129 csv_idx++;
130 else if (_csvtok_csv[csv_idx]=='\n')
131 csv_idx+=1;
132 else if ((_csvtok_csv[csv_idx]=='\r')&&(_csvtok_csv[csv_idx+1]=='\n'))
133 csv_idx+=2;
134 else if (_csvtok_csv[csv_idx]!=0) {
135 fprintf(stderr,"Parsing error after %s\n",csvtok_txt);
136 free(csvtok_txt);
137 _csvtok_csv=NULL;
138 return NULL;
139 }
140 _csvtok_csv += csv_idx;
141 return csvtok_txt;
142}
143
144/* RFC4180 compliant text to CSV encoder */
145/* https://www.rfc-editor.org/rfc/rfc4180 */
146char* txt2csv(const char* text) {
147 char* csv;
148 size_t text_idx;
149 size_t csv_idx;
150 unsigned short int need_quotes=0;
151
152 {
153 /* Check if quotes are needed and how many doublequotes are in the
154 * source text to allocate output buffer. This text iteration could be
155 * avoided but would imply to overallocate for the worst case scenario
156 * and to reallocate at the end with the potentially needed surrounding
157 * quotes */
158 size_t extra_chars = 0;
159 for (text_idx=0; text[text_idx]; text_idx++) {
160 if ( (text[text_idx]==',')|| (text[text_idx]=='\r')|| (text[text_idx]=='\n'))
161 need_quotes = 1;
162 if (text[text_idx]=='"') {
163 need_quotes = 1;
164 extra_chars++;
165 }
166 }
167 /* Allocate the right output buffer size */
168 if (NULL==(csv=malloc(strlen(text)+(need_quotes?2:0)+extra_chars+1))) {
169 perror("txt2csv malloc");
170 return NULL;
171 }
172 }
173
174 text_idx = 0;
175 csv_idx = 0;
176
177 /* If quotes are needed add a starting doublequote */
178 if (need_quotes)
179 csv[csv_idx++] = '"';
180
181 /* Copy each source char to the destination buffer */
182 while (text[text_idx]) {
183 /* With a doublequote before if the char to copy is a doublequote */
184 if (text[text_idx]=='"')
185 csv[csv_idx++] = '"';
186 csv[csv_idx++] = text[text_idx++];
187 }
188
189 /* If quotes are needed add a closing doublequote */
190 if (need_quotes)
191 csv[csv_idx++] = '"';
192
193 /* Properly end the C string */
194 csv[csv_idx] = 0;
195
196 return csv;
197}
198/* vim: set tw=80: */
void csv_addfield(FILE *reportfile, const char *value)
Definition: csv.c:37
void csv_addline(FILE *reportfile)
Definition: csv.c:32
char * txt2csv(const char *text)
Definition: csv.c:146
char * csvtok(char *source)
Definition: csv.c:50
<+DETAILED+>
#define NULL
Definition: rsstats-opts.c:64