File: | tokenlist.c |
Warning: | line 178, column 6 Although the value stored to 'n_entries' is used in the enclosing expression, the value is never actually read from 'n_entries' |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* |
2 | * This file is part of libdom. |
3 | * Licensed under the MIT License, |
4 | * http://www.opensource.org/licenses/mit-license.php |
5 | * Copyright 2022 Daniel Silverstone <dsilvers@digital-scurf.org> |
6 | */ |
7 | |
8 | #include <assert.h> |
9 | #include <stdlib.h> |
10 | #include <string.h> |
11 | |
12 | #include <dom/core/element.h> |
13 | #include <dom/core/nodelist.h> |
14 | #include <dom/core/tokenlist.h> |
15 | #include <dom/core/string.h> |
16 | #include <dom/events/event.h> |
17 | #include <dom/events/event_target.h> |
18 | #include <dom/events/event_listener.h> |
19 | #include <dom/events/mutation_event.h> |
20 | |
21 | #include "core/element.h" |
22 | #include "core/document.h" |
23 | |
24 | #include "utils/utils.h" |
25 | |
26 | #define DOM_TOKENLIST_GROW_INCREMENT4 4 |
27 | |
28 | struct dom_tokenlist { |
29 | uint32_t refcnt; |
30 | dom_element *ele; |
31 | dom_string *attr; |
32 | dom_event_listener *listener; |
33 | dom_string *last_set; |
34 | bool_Bool needs_parse; |
35 | /* Parsed content, for optimal access */ |
36 | dom_string **entries; |
37 | uint32_t len; |
38 | uint32_t alloc; |
39 | }; |
40 | |
41 | /* Handle a DOMAttrModified event which might be to do with our attribute */ |
42 | |
43 | static void _dom_tokenlist_handle_attrmodified(dom_event *evt, void *pw) |
44 | { |
45 | dom_mutation_event *mutevt = (dom_mutation_event *)evt; |
46 | dom_tokenlist *list = (dom_tokenlist *)pw; |
47 | dom_exception exc; |
48 | dom_string *value; |
49 | |
50 | { |
51 | dom_event_target *target; |
52 | exc = dom_event_get_target(evt, &target)_dom_event_get_target((dom_event *) (evt), (dom_event_target * *) (&target)); |
53 | if (exc != DOM_NO_ERR) |
54 | return; |
55 | dom_node_unref(target)dom_node_unref((dom_node *) (target)); |
56 | if (target != (dom_event_target *)list->ele) |
57 | return; |
58 | } |
59 | |
60 | { |
61 | dom_string *attr; |
62 | exc = dom_mutation_event_get_attr_name(mutevt, &attr)_dom_mutation_event_get_attr_name((dom_mutation_event *) (mutevt ), (dom_string **) (&attr)); |
63 | if (exc != DOM_NO_ERR) |
64 | return; |
65 | if (!dom_string_isequal(attr, list->attr)) { |
66 | dom_string_unref(attr); |
67 | return; |
68 | } |
69 | dom_string_unref(attr); |
70 | } |
71 | |
72 | /* At this point we know that this is a mutation of our attribute on our |
73 | * node */ |
74 | |
75 | exc = dom_mutation_event_get_new_value(mutevt, &value)_dom_mutation_event_get_new_value((dom_mutation_event *) (mutevt ), (dom_string **) (&value)); |
76 | if (exc != DOM_NO_ERR) |
77 | return; |
78 | |
79 | if (list->last_set != NULL((void*)0) && |
80 | dom_string_isequal(list->last_set, value)) { |
81 | /* We've just seen the mutation event for one of our own set |
82 | * operations */ |
83 | dom_string_unref(value); |
84 | return; |
85 | } |
86 | |
87 | /* Mark that we need to re-parse the tokenlist on the next request */ |
88 | list->needs_parse = true1; |
89 | |
90 | dom_string_unref(value); |
91 | } |
92 | |
93 | static dom_exception _dom_tokenlist_make_room(dom_tokenlist *list) |
94 | { |
95 | if (list->len == list->alloc) { |
96 | uint32_t new_alloc = list->alloc + DOM_TOKENLIST_GROW_INCREMENT4; |
97 | dom_string **new_entries = realloc( |
98 | list->entries, new_alloc * sizeof(dom_string *)); |
99 | if (new_entries == NULL((void*)0)) |
100 | return DOM_NO_MEM_ERR; |
101 | list->alloc = new_alloc; |
102 | list->entries = new_entries; |
103 | } |
104 | |
105 | return DOM_NO_ERR; |
106 | } |
107 | |
108 | static dom_exception _dom_tokenlist_reparse(dom_tokenlist *list) |
109 | { |
110 | dom_exception exc; |
111 | dom_string *value; |
112 | const char *pos; |
113 | uint32_t remaining, check; |
114 | uint32_t n_entries = 0; |
115 | dom_string *temp; |
116 | bool_Bool found; |
117 | |
118 | if (!list->needs_parse) |
119 | return DOM_NO_ERR; |
120 | |
121 | /* Clean down the current entries */ |
122 | while (list->len-- > 0) |
123 | dom_string_unref(list->entries[list->len]); |
124 | list->len = 0; |
125 | |
126 | /* Get the "new" attribute value */ |
127 | exc = dom_element_get_attribute(list->ele, list->attr, &value)dom_element_get_attribute( (dom_element *) (list->ele), (list ->attr), (&value)); |
128 | if (exc != DOM_NO_ERR) |
129 | return exc; |
130 | |
131 | /* If there is no value, we're an empty list and we're done */ |
132 | if (value == NULL((void*)0)) { |
133 | list->needs_parse = false0; |
134 | return DOM_NO_ERR; |
135 | } |
136 | |
137 | /* OK, there's something here to do, so let's do it... */ |
138 | |
139 | /* Count number of entries */ |
140 | for (pos = dom_string_data(value), remaining = dom_string_length(value); |
141 | remaining > 0;) { |
142 | if (*pos != ' ') { |
143 | while (*pos != ' ' && remaining > 0) { |
144 | remaining--; |
145 | pos++; |
146 | } |
147 | n_entries++; |
148 | } else { |
149 | while (*pos == ' ' && remaining > 0) { |
150 | remaining--; |
151 | pos++; |
152 | } |
153 | } |
154 | } |
155 | |
156 | /* If there are no entries (all whitespace) just bail here */ |
157 | if (n_entries == 0) { |
158 | list->needs_parse = false0; |
159 | dom_string_unref(value); |
160 | return DOM_NO_ERR; |
161 | } |
162 | |
163 | /* If we need more room, reallocate the buffer */ |
164 | if (list->alloc < n_entries) { |
165 | dom_string **new_alloc = realloc( |
166 | list->entries, n_entries * sizeof(dom_string *)); |
167 | if (new_alloc == NULL((void*)0)) { |
168 | dom_string_unref(value); |
169 | return DOM_NO_MEM_ERR; |
170 | } |
171 | list->entries = new_alloc; |
172 | list->alloc = n_entries; |
173 | } |
174 | |
175 | /* And now parse those entries into the buffer */ |
176 | for (pos = dom_string_data(value), |
177 | remaining = dom_string_length(value), |
178 | n_entries = 0; |
Although the value stored to 'n_entries' is used in the enclosing expression, the value is never actually read from 'n_entries' | |
179 | remaining > 0;) { |
180 | if (*pos != ' ') { |
181 | const char *s = pos; |
182 | while (*pos != ' ' && remaining > 0) { |
183 | pos++; |
184 | remaining--; |
185 | } |
186 | exc = dom_string_create_interned((const uint8_t *)s, |
187 | pos - s, |
188 | &temp); |
189 | if (exc != DOM_NO_ERR) { |
190 | dom_string_unref(value); |
191 | return exc; |
192 | } |
193 | found = false0; |
194 | for (check = 0; check < list->len; check++) { |
195 | if (dom_string_isequal(temp, |
196 | list->entries[check])) { |
197 | found = true1; |
198 | break; |
199 | } |
200 | } |
201 | if (found == true1) { |
202 | dom_string_unref(temp); |
203 | } else { |
204 | list->entries[list->len] = temp; |
205 | list->len++; |
206 | } |
207 | } else { |
208 | while (*pos == ' ' && remaining > 0) { |
209 | pos++; |
210 | remaining--; |
211 | } |
212 | } |
213 | } |
214 | |
215 | dom_string_unref(value); |
216 | list->needs_parse = false0; |
217 | |
218 | return DOM_NO_ERR; |
219 | } |
220 | |
221 | static dom_exception _dom_tokenlist_reify(dom_tokenlist *list) |
222 | { |
223 | dom_exception exc; |
224 | uint32_t nchars = 0, n; |
225 | char *buffer, *next; |
226 | dom_string *output; |
227 | |
228 | if (list->len == 0) { |
229 | if (list->last_set != NULL((void*)0)) { |
230 | dom_string_unref(list->last_set); |
231 | } |
232 | list->last_set = dom_string_ref( |
233 | list->ele->base.owner->_memo_empty); |
234 | return dom_element_set_attribute(list->ele,dom_element_set_attribute( (dom_element *) (list->ele), (list ->attr), (list->last_set)) |
235 | list->attr,dom_element_set_attribute( (dom_element *) (list->ele), (list ->attr), (list->last_set)) |
236 | list->last_set)dom_element_set_attribute( (dom_element *) (list->ele), (list ->attr), (list->last_set)); |
237 | } |
238 | |
239 | for (n = 0; n < list->len; ++n) |
240 | nchars += dom_string_length(list->entries[n]); |
241 | |
242 | buffer = calloc(1, nchars + list->len); |
243 | if (buffer == NULL((void*)0)) |
244 | return DOM_NO_MEM_ERR; |
245 | |
246 | for (next = buffer, n = 0; n < list->len; ++n) { |
247 | uint32_t slen = dom_string_length(list->entries[n]); |
248 | memcpy(next, dom_string_data(list->entries[n]), slen); |
249 | next[slen] = ' '; |
250 | next += slen + 1; |
251 | } |
252 | |
253 | exc = dom_string_create_interned((const uint8_t *)buffer, |
254 | nchars + list->len - 1, |
255 | &output); |
256 | free(buffer); |
257 | if (exc != DOM_NO_ERR) |
258 | return exc; |
259 | |
260 | if (list->last_set != NULL((void*)0)) { |
261 | dom_string_unref(list->last_set); |
262 | } |
263 | list->last_set = output; |
264 | |
265 | return dom_element_set_attribute(list->ele, list->attr, list->last_set)dom_element_set_attribute( (dom_element *) (list->ele), (list ->attr), (list->last_set)); |
266 | } |
267 | |
268 | /**********************************************************************************/ |
269 | |
270 | /** |
271 | * Create a tokenlist |
272 | * |
273 | * \param ele The element which owns the tokenlist attribute |
274 | * \param attr The name of the attribute we are treating as a tokenlist |
275 | * \param list The tokenlist output which is set on success |
276 | * \return DOM_NO_ERR on success, DOM_NO_MEM_ERR on memory exhaustion |
277 | * |
278 | * The returned list will already be referenced, so the client need not |
279 | * do so explicitly. The client must unref the list once finished with it. |
280 | * |
281 | * This list will take its own references to ::ele and ::attr |
282 | */ |
283 | dom_exception |
284 | dom_tokenlist_create(dom_element *ele, dom_string *attr, dom_tokenlist **list) |
285 | { |
286 | dom_tokenlist *l; |
287 | dom_exception exc; |
288 | |
289 | l = calloc(1, sizeof(dom_tokenlist)); |
290 | if (l == NULL((void*)0)) |
291 | return DOM_NO_MEM_ERR; |
292 | |
293 | l->refcnt = 1; |
294 | l->ele = (dom_element *)dom_node_ref(ele)dom_node_ref((dom_node *) (ele)); |
295 | l->attr = dom_string_ref(attr); |
296 | l->needs_parse = true1; |
297 | |
298 | exc = dom_event_listener_create(_dom_tokenlist_handle_attrmodified, |
299 | l, |
300 | &l->listener); |
301 | if (exc != DOM_NO_ERR) |
302 | goto fail; |
303 | |
304 | exc = dom_event_target_add_event_listener(dom_event_target_add_event_listener((dom_event_target *) (ele ), (dom_string *) (ele->base.owner->_memo_domattrmodified ), (struct dom_event_listener *) (l->listener), (_Bool) (0 )) |
305 | ele,dom_event_target_add_event_listener((dom_event_target *) (ele ), (dom_string *) (ele->base.owner->_memo_domattrmodified ), (struct dom_event_listener *) (l->listener), (_Bool) (0 )) |
306 | ele->base.owner->_memo_domattrmodified,dom_event_target_add_event_listener((dom_event_target *) (ele ), (dom_string *) (ele->base.owner->_memo_domattrmodified ), (struct dom_event_listener *) (l->listener), (_Bool) (0 )) |
307 | l->listener,dom_event_target_add_event_listener((dom_event_target *) (ele ), (dom_string *) (ele->base.owner->_memo_domattrmodified ), (struct dom_event_listener *) (l->listener), (_Bool) (0 )) |
308 | false)dom_event_target_add_event_listener((dom_event_target *) (ele ), (dom_string *) (ele->base.owner->_memo_domattrmodified ), (struct dom_event_listener *) (l->listener), (_Bool) (0 )); |
309 | |
310 | if (exc != DOM_NO_ERR) |
311 | goto fail; |
312 | |
313 | *list = l; |
314 | |
315 | return DOM_NO_ERR; |
316 | |
317 | fail: |
318 | if (l->listener != NULL((void*)0)) |
319 | dom_event_listener_unref(l->listener); |
320 | dom_node_unref(l->ele)dom_node_unref((dom_node *) (l->ele)); |
321 | dom_string_unref(l->attr); |
322 | free(l); |
323 | return exc; |
324 | } |
325 | |
326 | /** |
327 | * Claim a ref on a tokenlist |
328 | * |
329 | * \param list The tokenlist to claim a ref on |
330 | */ |
331 | void dom_tokenlist_ref(dom_tokenlist *list) |
332 | { |
333 | assert(list != NULL)((list != ((void*)0)) ? (void) (0) : __assert_fail ("list != NULL" , "src/core/tokenlist.c", 333, __extension__ __PRETTY_FUNCTION__ )); |
334 | list->refcnt++; |
335 | } |
336 | |
337 | /** |
338 | * Release a ref on a tokenlist |
339 | * |
340 | * \param list The list to release the reference of |
341 | * |
342 | * If you release the last ref, this cleans up the tokenlist |
343 | */ |
344 | void dom_tokenlist_unref(dom_tokenlist *list) |
345 | { |
346 | assert(list != NULL)((list != ((void*)0)) ? (void) (0) : __assert_fail ("list != NULL" , "src/core/tokenlist.c", 346, __extension__ __PRETTY_FUNCTION__ )); |
347 | |
348 | if (--list->refcnt > 0) |
349 | return; |
350 | |
351 | if (list->alloc > 0) { |
352 | while (list->len-- > 0) |
353 | dom_string_unref(list->entries[list->len]); |
354 | free(list->entries); |
355 | } |
356 | |
357 | dom_event_target_remove_event_listener(dom_event_target_remove_event_listener( (dom_event_target *) ( list->ele), (dom_string *) (list->ele->base.owner-> _memo_domattrmodified), (struct dom_event_listener *) (list-> listener), (_Bool) (0)) |
358 | list->ele,dom_event_target_remove_event_listener( (dom_event_target *) ( list->ele), (dom_string *) (list->ele->base.owner-> _memo_domattrmodified), (struct dom_event_listener *) (list-> listener), (_Bool) (0)) |
359 | list->ele->base.owner->_memo_domattrmodified,dom_event_target_remove_event_listener( (dom_event_target *) ( list->ele), (dom_string *) (list->ele->base.owner-> _memo_domattrmodified), (struct dom_event_listener *) (list-> listener), (_Bool) (0)) |
360 | list->listener,dom_event_target_remove_event_listener( (dom_event_target *) ( list->ele), (dom_string *) (list->ele->base.owner-> _memo_domattrmodified), (struct dom_event_listener *) (list-> listener), (_Bool) (0)) |
361 | false)dom_event_target_remove_event_listener( (dom_event_target *) ( list->ele), (dom_string *) (list->ele->base.owner-> _memo_domattrmodified), (struct dom_event_listener *) (list-> listener), (_Bool) (0)); |
362 | |
363 | dom_event_listener_unref(list->listener); |
364 | |
365 | if (list->last_set != NULL((void*)0)) |
366 | dom_string_unref(list->last_set); |
367 | |
368 | dom_string_unref(list->attr); |
369 | dom_node_unref(list->ele)dom_node_unref((dom_node *) (list->ele)); |
370 | |
371 | free(list); |
372 | } |
373 | |
374 | /** |
375 | * Get the length of the tokenlist |
376 | * |
377 | * \param list The list to get the length of |
378 | * \param length Length of the list outputs here |
379 | * \return DOM_NO_ERR on success, otherwise the failure code |
380 | */ |
381 | dom_exception dom_tokenlist_get_length(dom_tokenlist *list, uint32_t *length) |
382 | { |
383 | dom_exception exc; |
384 | assert(list != NULL)((list != ((void*)0)) ? (void) (0) : __assert_fail ("list != NULL" , "src/core/tokenlist.c", 384, __extension__ __PRETTY_FUNCTION__ )); |
385 | |
386 | exc = _dom_tokenlist_reparse(list); |
387 | if (exc != DOM_NO_ERR) |
388 | return exc; |
389 | |
390 | *length = list->len; |
391 | |
392 | return DOM_NO_ERR; |
393 | } |
394 | |
395 | /** |
396 | * Get a particular item from the tokenlist |
397 | * |
398 | * \param list The list to retrieve the item from |
399 | * \param index The index of the item to retrieve |
400 | * \param value The value of the item returns here |
401 | * \return DOM_NO_ERR on success, otherwise the failure code |
402 | */ |
403 | dom_exception |
404 | _dom_tokenlist_item(dom_tokenlist *list, uint32_t index, dom_string **value) |
405 | { |
406 | dom_exception exc; |
407 | assert(list != NULL)((list != ((void*)0)) ? (void) (0) : __assert_fail ("list != NULL" , "src/core/tokenlist.c", 407, __extension__ __PRETTY_FUNCTION__ )); |
408 | |
409 | exc = _dom_tokenlist_reparse(list); |
410 | if (exc != DOM_NO_ERR) |
411 | return exc; |
412 | |
413 | if (index >= list->len) { |
414 | *value = NULL((void*)0); |
415 | return DOM_NO_ERR; |
416 | } |
417 | |
418 | *value = dom_string_ref(list->entries[index]); |
419 | return DOM_NO_ERR; |
420 | } |
421 | |
422 | /** |
423 | * Retrieve the value of the tokenlist as a string |
424 | * |
425 | * \param list The list to retrieve the value of |
426 | * \param value The value of the list returns here |
427 | * \return DOM_NO_ERR on success, otherwise the failure code |
428 | */ |
429 | dom_exception dom_tokenlist_get_value(dom_tokenlist *list, dom_string **value) |
430 | { |
431 | assert(list != NULL)((list != ((void*)0)) ? (void) (0) : __assert_fail ("list != NULL" , "src/core/tokenlist.c", 431, __extension__ __PRETTY_FUNCTION__ )); |
432 | |
433 | return dom_element_get_attribute(list->ele, list->attr, value)dom_element_get_attribute( (dom_element *) (list->ele), (list ->attr), (value)); |
434 | } |
435 | |
436 | /** |
437 | * Set the value of the tokenlist as a string |
438 | * |
439 | * \param list The list to set the value of |
440 | * \param value The value to set |
441 | * \return DOM_NO_ERR on success, otherwise the failure code |
442 | * |
443 | */ |
444 | dom_exception dom_tokenlist_set_value(dom_tokenlist *list, dom_string *value) |
445 | { |
446 | assert(list != NULL)((list != ((void*)0)) ? (void) (0) : __assert_fail ("list != NULL" , "src/core/tokenlist.c", 446, __extension__ __PRETTY_FUNCTION__ )); |
447 | |
448 | return dom_element_set_attribute(list->ele, list->attr, value)dom_element_set_attribute( (dom_element *) (list->ele), (list ->attr), (value)); |
449 | } |
450 | |
451 | /** |
452 | * Check if the given value is in the tokenlist |
453 | * |
454 | * \param list The list to scan for the given value |
455 | * \param value The value to look for in the token list |
456 | * \param contains This will be set based on whether or not the value is present |
457 | * \return DOM_NO_ERR on success, otherwise the failure code |
458 | */ |
459 | dom_exception |
460 | dom_tokenlist_contains(dom_tokenlist *list, dom_string *value, bool_Bool *contains) |
461 | { |
462 | dom_exception exc; |
463 | uint32_t n; |
464 | |
465 | assert(list != NULL)((list != ((void*)0)) ? (void) (0) : __assert_fail ("list != NULL" , "src/core/tokenlist.c", 465, __extension__ __PRETTY_FUNCTION__ )); |
466 | |
467 | exc = _dom_tokenlist_reparse(list); |
468 | if (exc != DOM_NO_ERR) |
469 | return exc; |
470 | |
471 | *contains = false0; |
472 | |
473 | for (n = 0; n < list->len; n++) { |
474 | if (dom_string_isequal(value, list->entries[n])) { |
475 | *contains = true1; |
476 | break; |
477 | } |
478 | } |
479 | |
480 | return DOM_NO_ERR; |
481 | } |
482 | |
483 | /** |
484 | * Add the given value to the tokenlist |
485 | * |
486 | * \param list The list to add to |
487 | * \param value The value to add |
488 | * \return DOM_NO_ERR on success, otherwise the failure code |
489 | */ |
490 | dom_exception dom_tokenlist_add(dom_tokenlist *list, dom_string *value) |
491 | { |
492 | dom_exception exc; |
493 | bool_Bool present = false0; |
494 | |
495 | assert(list != NULL)((list != ((void*)0)) ? (void) (0) : __assert_fail ("list != NULL" , "src/core/tokenlist.c", 495, __extension__ __PRETTY_FUNCTION__ )); |
496 | |
497 | exc = dom_tokenlist_contains(list, value, &present); |
498 | if (exc != DOM_NO_ERR) |
499 | return exc; |
500 | |
501 | if (present == true1) |
502 | return DOM_NO_ERR; |
503 | |
504 | exc = _dom_tokenlist_make_room(list); |
505 | if (exc != DOM_NO_ERR) |
506 | return exc; |
507 | |
508 | list->entries[list->len++] = dom_string_ref(value); |
509 | |
510 | exc = _dom_tokenlist_reify(list); |
511 | |
512 | return exc; |
513 | } |
514 | |
515 | /** |
516 | * Remove the given value from the tokenlist |
517 | * |
518 | * \param list The list to remove from |
519 | * \param value The value to remove |
520 | * \return DOM_NO_ERR on success, otherwise the failure code |
521 | */ |
522 | dom_exception dom_tokenlist_remove(dom_tokenlist *list, dom_string *value) |
523 | { |
524 | dom_exception exc; |
525 | uint32_t n, m; |
526 | |
527 | assert(list != NULL)((list != ((void*)0)) ? (void) (0) : __assert_fail ("list != NULL" , "src/core/tokenlist.c", 527, __extension__ __PRETTY_FUNCTION__ )); |
528 | |
529 | exc = _dom_tokenlist_reparse(list); |
530 | if (exc != DOM_NO_ERR) |
531 | return false0; |
532 | |
533 | for (n = 0; n < list->len; ++n) { |
534 | if (dom_string_isequal(value, list->entries[n])) { |
535 | dom_string_unref(list->entries[n]); |
536 | for (m = n + 1; m < list->len; ++m) { |
537 | list->entries[m - 1] = list->entries[m]; |
538 | } |
539 | list->len--; |
540 | break; |
541 | } |
542 | } |
543 | |
544 | exc = _dom_tokenlist_reify(list); |
545 | |
546 | return exc; |
547 | } |