src/properties.c

changeset 924
3c90dfc35f06
parent 923
45da884269c8
child 928
d2d42cb1d59e
equal deleted inserted replaced
923:45da884269c8 924:3c90dfc35f06
26 * POSSIBILITY OF SUCH DAMAGE. 26 * POSSIBILITY OF SUCH DAMAGE.
27 */ 27 */
28 28
29 #include "cx/properties.h" 29 #include "cx/properties.h"
30 30
31 #include <string.h>
32 #include <assert.h>
33
34 static const int CX_PROPERTIES_FLAG_USE_STACK = 0x01;
35
31 const CxPropertiesConfig cx_properties_config_default = { 36 const CxPropertiesConfig cx_properties_config_default = {
32 '=', 37 '=',
33 '\\', 38 //'\\',
34 '#', 39 '#',
35 '\0', 40 '\0',
36 '\0' 41 '\0'
37 }; 42 };
43
44 void cxPropertiesInit(
45 CxProperties *prop,
46 CxPropertiesConfig config
47 ) {
48 memset(prop, 0, sizeof(CxProperties));
49 prop->config = config;
50 }
51
52 void cxPropertiesDestroy(CxProperties *prop) {
53 if (0 == (prop->flags & CX_PROPERTIES_FLAG_USE_STACK)) {
54 free(prop->buf);
55 }
56 prop->buf = NULL;
57 prop->buf_capacity = prop->buf_size = 0;
58 }
59
60 static int cx_properties_ensure_buf_capacity(CxProperties *prop, size_t cap) {
61 if (prop->buf_capacity >= cap) {
62 return 0;
63 }
64
65 // not enough capacity - are we on the stack right now?
66 if ((prop->flags & CX_PROPERTIES_FLAG_USE_STACK) != 0) {
67 // move to the heap
68 char *newbuf = malloc(cap);
69 if (newbuf == NULL) return 1;
70 memcpy(newbuf, prop->buf, prop->buf_size);
71 prop->buf = newbuf;
72 prop->flags &= CX_PROPERTIES_FLAG_USE_STACK;
73 } else {
74 // we are on the heap already, reallocate
75 // this is legit, because realloc() behaves like malloc() when the
76 // current pointer is NULL
77 char *newbuf = realloc(prop->buf, cap);
78 if (newbuf == NULL) return 1;
79 prop->buf = newbuf;
80 }
81
82 // store new capacity and return
83 prop->buf_capacity = cap;
84 return 0;
85 }
86
87 static int cx_properties_rescuen_input(CxProperties *prop, size_t len) {
88 if (cx_properties_ensure_buf_capacity(prop, prop->buf_size + len)) {
89 return 1;
90 }
91 const char *src = prop->text + prop->text_pos;
92 char *dest = prop->buf + prop->buf_size;
93 memcpy(dest, src, len);
94 prop->buf_size += len;
95 return 0;
96 }
97
98 static int cx_properties_rescue_input(CxProperties *prop) {
99 // someone fucked around with our integers, exit immediately
100 if (prop->text_pos > prop->text_size) return 0;
101
102 // determine the bytes needed
103 size_t len = prop->text_size - prop->text_pos;
104
105 return cx_properties_rescuen_input(prop, len);
106 }
107
108 void cxPropertiesInput(
109 CxProperties *prop,
110 const char *buf,
111 size_t len
112 ) {
113 prop->text = buf;
114 prop->text_size = len;
115 prop->text_pos = 0;
116 }
117
118 int cxPropertiesFill(
119 CxProperties *prop,
120 const char *buf,
121 size_t len
122 ) {
123 if (cx_properties_rescue_input(prop)) return 1;
124 cxPropertiesInput(prop, buf, len);
125 return 0;
126 }
127
128 void cxPropertiesUseStack(
129 CxProperties *prop,
130 char *buf,
131 size_t capacity
132 ) {
133 assert(buf == NULL);
134 prop->buf = buf;
135 prop->buf_capacity = capacity;
136 prop->buf_size = 0;
137 prop->flags |= CX_PROPERTIES_FLAG_USE_STACK;
138 }
139
140 enum cx_properties_status cxPropertiesNext(
141 CxProperties *prop,
142 cxstring *key,
143 cxstring *value
144 ) {
145 // check if we have a text buffer
146 if (prop->text == NULL) {
147 return CX_PROPERTIES_NULL_INPUT;
148 }
149 // check if we have rescued data
150 if (prop->buf_size > 0) {
151 // check if we can now get a complete line
152 const char *buf = prop->text + prop->text_pos;
153 size_t len = prop->text_size - prop->text_pos;
154 cxstring str = cx_strn(buf, len);
155 cxstring nl = cx_strchr(str, '\n');
156 if(nl.length > 0) {
157 // we add as much data to the rescue buffer as we need
158 // to complete the line
159 size_t len_until_nl = (size_t)(nl.ptr - buf) + 1;
160
161 if (cx_properties_rescuen_input(prop, len_until_nl)) {
162 return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
163 }
164
165 // the tmp buffer contains exactly one line now
166 // we use a trick here: we swap the buffers and recurse
167 const char *orig_text = prop->text;
168 size_t orig_size = prop->text_size;
169 prop->text = prop->buf;
170 prop->text_size = prop->buf_size;
171 prop->text_pos = 0;
172 prop->buf_size = 0;
173
174 enum cx_properties_status result;
175 result = cxPropertiesNext(prop, key, value);
176
177 // restore original buffer
178 prop->text = orig_text;
179 prop->text_size = orig_size;
180
181 // set the position to after the newline
182 prop->text_pos = len_until_nl;
183
184 // check the result
185 if (result == CX_PROPERTIES_NO_ERROR) {
186 // reset the rescue buffer and return with the result
187 prop->buf_size = 0;
188 return result;
189 } else if (result == CX_PROPERTIES_NO_DATA) {
190 // rescue buffer contained only blanks or comments
191 // reset the rescue buffer and retry with text buffer
192 prop->buf_size = 0;
193 return cxPropertiesNext(prop, key, value);
194 } else {
195 // CX_PROPERTIES_INCOMPLETE_DATA is not possible
196 // so it must have been another error
197 // do not reset the rescue buffer and return the error
198 return result;
199 }
200 } else {
201 // still not enough data
202 return CX_PROPERTIES_INCOMPLETE_DATA;
203 }
204 }
205
206 char comment1 = prop->config.comment1;
207 char comment2 = prop->config.comment2;
208 char comment3 = prop->config.comment3;
209 char delimiter = prop->config.delimiter;
210
211 // get one line and parse it
212 while (prop->text_pos < prop->text_size) {
213 const char *buf = prop->text + prop->text_pos;
214 size_t len = prop->text_size - prop->text_pos;
215
216 /*
217 * First we check if we have at least one line. We also get indices of
218 * delimiter and comment chars
219 */
220 size_t delimiter_index = 0;
221 size_t comment_index = 0;
222 bool has_comment = false;
223
224 size_t i = 0;
225 char c = 0;
226 for (; i < len; i++) {
227 c = buf[i];
228 if (c == comment1 || c == comment2 || c == comment3) {
229 if (comment_index == 0) {
230 comment_index = i;
231 has_comment = true;
232 }
233 } else if (c == delimiter) {
234 if (delimiter_index == 0 && !has_comment) {
235 delimiter_index = i;
236 }
237 } else if (c == '\n') {
238 break;
239 }
240 }
241
242 if (c != '\n') {
243 // we don't have enough data for a line
244 return CX_PROPERTIES_INCOMPLETE_DATA;
245 }
246
247 cxstring line = has_comment ?
248 cx_strn(buf, comment_index) :
249 cx_strn(buf, i);
250 // check line
251 if (delimiter_index == 0) {
252 // if line is not blank ...
253 line = cx_strtrim(line);
254 // ... either no delimiter found, or key is empty
255 if (line.length > 0) {
256 if (line.ptr[0] == delimiter) {
257 return CX_PROPERTIES_INVALID_EMPTY_KEY;
258 } else {
259 return CX_PROPERTIES_INVALID_MISSING_DELIMITER;
260 }
261 }
262 } else {
263 cxstring k = cx_strn(buf, delimiter_index);
264 cxstring val = cx_strn(
265 buf + delimiter_index + 1,
266 line.length - delimiter_index - 1);
267 k = cx_strtrim(k);
268 val = cx_strtrim(val);
269 if (k.length > 0) {
270 *key = k;
271 *value = val;
272 prop->text_pos += i + 1;
273 assert(prop->text_pos <= prop->text_size);
274 return CX_PROPERTIES_NO_ERROR;
275 } else {
276 return CX_PROPERTIES_INVALID_EMPTY_KEY;
277 }
278 }
279
280 prop->text_pos += i + 1;
281 }
282
283 // when we come to this point, all data must have been read
284 assert(prop->text_pos == prop->text_size);
285 return CX_PROPERTIES_NO_DATA;
286 }

mercurial