2 * Copyright Neil Brown ©2015-2021 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
7 * Attributes are attached to text in buffers and to marks and probably
9 * They are simply name=value pairs, stored as strings though direct
10 * conversion to numbers and Bools is provided.
11 * Values must be "small". The name and value together must be less than
12 * 512 bytes, and there is probably some padding in there. If you get
13 * even close to this limit you are doing something wrong.
14 * Larger strings need to be stored elsewhere with some sort of indirect.
15 * (Hmmm.. this excludes file names - is that a good idea?)
17 * Attributes are stored in a list sorted by attribute name. Strings
18 * of digits in the name sort like the number they represent, so "6hello"
19 * comes before "10world". When such a number compares against a single
20 * non-digit character the char comes first.
22 * Attributes for text are stored in one list for a section of text.
23 * Each attribute is prefixed by the offset where the attribute applies.
25 * The offsets are really byte offsets - the text is utf-8.
27 * When attributes are stored on non-text objects they don't have
39 unsigned short size, /* space allocated */
45 /* NOTE: this MAX is the largest sized allocation which
46 * can be shared by multiple attrs. The size is the sum
47 * of keys and values including nul terminator.
48 * If an attr is larger, it gets an attrset all of its own.
50 #if defined(TEST_ATTR_ADD_DEL)
51 #define MAX_ATTR_SIZE (64 - sizeof(struct attrset))
53 #define MAX_ATTR_SIZE (512 - sizeof(struct attrset))
56 static struct attrset *safe newattr(struct attrset *old, int size)
58 struct attrset *set = realloc(old, sizeof(struct attrset) + size);
67 /* attr_cmp just deals with bytes and ASCII digits, so it is
68 * not aware for wchars
70 static int getcmptok(const char **ap safe)
77 /* FIXME smatch should handle "char * safe *ap safe" */
94 /* Compare 'a' and 'b' treating strings of digits as numbers.
95 * If bnum >= 0, it is used as a leading number on 'b'.
97 static int attr_cmp(const char *a safe, const char *b safe, int bnum)
99 while (*a && (*b || bnum >= 0)) {
124 struct {const char *a, *b; int result;} test[] = {
125 { "hello", "there", -1},
126 { "6hello", "10world", -1},
127 { "0005six", "5six", 0},
130 int main(int argc, char *argv[])
134 for (i = 0; i < sizeof(test)/sizeof(test[0]); i++) {
135 if (attr_cmp(test[i].a, test[i].b, -1) == test[i].result)
136 printf("%s <-> %s = %d OK\n",
137 test[i].a, test[i].b, test[i].result);
139 printf("%s <-> %s = %d, not %d\n",
140 test[i].a, test[i].b, attr_cmp(test[i].a,
151 static int _attr_find(struct attrset ***setpp safe, const char *key safe,
152 int *offsetp safe, int keynum)
154 struct attrset **setp safe;
159 /* FIXME smatch should check this */
167 attr_cmp(set->next->attrs, key, keynum) <= 0) {
173 for (i = 0; i < set->len; ) {
174 int cmp = attr_cmp(set->attrs + i, key, keynum);
180 i += strlen(set->attrs + i) + 1;
181 i += strlen(set->attrs + i) + 1;
187 static void do_del(struct attrset * *setp safe, int offset)
192 set = safe_cast *setp;
193 len = strlen(set->attrs + offset) + 1;
194 len += strlen(set->attrs + offset + len) + 1;
195 memmove(set->attrs + offset,
196 set->attrs + offset + len,
197 set->len - (offset + len));
205 bool attr_del(struct attrset * *setp safe, const char *key safe)
210 cmp = _attr_find(&setp, key, &offset, -1);
215 do_del(setp, offset);
219 void attr_del_all(struct attrset * *setp safe, const char *key safe,
223 /* Delete all attrs 'key' with keynum from 'low' to 'high' */
224 while (low <= high) {
227 int cmp = _attr_find(&setp, key, &offset, low);
230 /* Nothing more to find */
234 /* Found, better delete */
235 do_del(setp, offset);
238 /* found something higher, possibly update 'low'
242 if (!set || offset >= set->len)
244 n = atoi(set->attrs + offset);
250 char *attr_get_str(struct attrset *set, const char *key safe, int keynum)
252 struct attrset **setp = &set;
254 int cmp = _attr_find(&setp, key, &offset, keynum);
256 if (cmp != 0 || !*setp)
259 offset += strlen(set->attrs + offset) + 1;
260 return set->attrs + offset;
263 char *attr_find(struct attrset *set, const char *key safe)
265 return attr_get_str(set, key, -1);
268 const char *attr_get_next_key(struct attrset *set, const char *key safe,
269 int keynum, const char **valp safe)
271 struct attrset **setp = &set;
273 int cmp = _attr_find(&setp, key, &offset, keynum);
278 set = safe_cast *setp;
280 /* Skip the matching key, then value */
281 offset += strlen(set->attrs + offset) + 1;
282 offset += strlen(set->attrs + offset) + 1;
284 if (offset >= set->len) {
290 key = set->attrs + offset;
291 val = key + strlen(key) + 1;
293 int kn = getcmptok(&key);
294 if (kn != keynum + 256)
303 int attr_set_str_key(struct attrset **setp safe,
304 const char *key safe, const char *val,
310 unsigned int len, keylen, vallen;
314 cmp = _attr_find(&setp, key, &offset, keynum);
317 /* Remove old value */
318 do_del(setp, offset);
325 snprintf(nkey, sizeof(nkey), "%d ", keynum);
326 nkeylen = strlen(nkey);
329 keylen = strlen(key);
330 vallen = strlen(val);
331 len = nkeylen + keylen + 1 + vallen + 1;
332 while (set == NULL || set->len + len > set->size) {
333 /* Need to re-alloc or alloc new */
334 if (!set || (offset == 0 && len + set->len > MAX_ATTR_SIZE)) {
335 /* Create a new set - which may exceed MAX_ATTR_SIZE */
336 struct attrset *new = newattr(NULL, len);
340 } else if (set->len + len <= MAX_ATTR_SIZE) {
341 /* Just make this block bigger */
342 set = newattr(set, set->len + len);
344 } else if (offset + len <= MAX_ATTR_SIZE) {
345 /* split following entries in separate block */
346 struct attrset *new = newattr(NULL, set->len - offset);
347 new->next = set->next;
349 new->len = set->len - offset;
351 memcpy(new->attrs, set->attrs + offset,
353 } else if (offset == set->len) {
354 /* Need to add after here */
359 /* offset must be > 0.
360 * Split off following entries and try again there.
362 struct attrset *new = newattr(NULL,
365 new->next = set->next;
367 new->len = set->len - offset;
369 memcpy(new->attrs, set->attrs + offset,
376 memmove(set->attrs + offset + len, set->attrs + offset,
378 memcpy(set->attrs + offset, nkey, nkeylen);
379 memcpy(set->attrs + offset + nkeylen, key, keylen + 1);
380 memcpy(set->attrs + offset + nkeylen + keylen + 1, val, vallen + 1);
385 int attr_set_str(struct attrset **setp safe,
386 const char *key safe, const char *val)
388 return attr_set_str_key(setp, key, val, -1);
391 #if defined(TEST_ATTR_ADD_DEL) || defined(TEST_ATTR_TRIM)
392 void attr_dump(struct attrset *set)
394 printf("DUMP ATTRS:\n");
397 printf(" %d of %d:\n", set->len, set->size);
398 for (i = 0; i < set->len; ) {
399 printf(" %3d: \"%s\"", i, set->attrs + i);
400 i += strlen(set->attrs+i) + 1;
401 printf(" -> \"%s\"\n", set->attrs + i);
402 i += strlen(set->attrs+i) + 1;
406 printf("END DUMP\n");
410 #ifdef TEST_ATTR_ADD_DEL
411 enum act { Add, Remove, Find };
417 { Add, "Hello", "world"},
418 { Add, "05 Foo", "Bar" },
419 { Add, "1 Bold", "off" },
420 { Add, "9 Underline", "on" },
421 { Remove, "Hello", NULL },
422 { Find, "5 Foo", "Bar" },
423 { Add, "20 Thing", "Stuff" },
424 { Add, "01 Bold", "on" },
425 { Add, "1 StrikeThrough", "no" },
426 { Add, "2 StrikeThrough", "no" },
427 { Find, "1 StrikeThrough", "no" },
428 { Find, "5 Foo", "Bar" },
429 { Add, "1 Nextthing", "nonono" },
432 int main(int argc, char *argv[])
436 struct attrset *set = NULL;
437 for (i = 0; i < sizeof(actions)/sizeof(actions[0]); i++) {
438 struct action *a = &actions[i];
442 attr_set_str(&set, a->key, a->val); continue;
444 if (!attr_del(&set, a->key)) {
445 printf("Action %d: Remove %s: failed\n",
452 v = attr_find(set, a->key);
453 if (!v || strcmp(v, a->val) != 0) {
454 printf("Action %d: Find %s: Found %s\n",
468 /* Have versions that take and return numbers, '-1' for 'not found' */
469 int attr_find_int(struct attrset *set, const char *key safe)
471 const char *val = attr_find(set, key);
477 rv = strtoul(val, &end, 10);
478 if (!end || end == val || *end)
483 int attr_set_int(struct attrset **setp safe, const char *key safe, int val)
485 /* 3 digits per bytes, +1 for sign and +1 for trailing nul */
486 char sval[sizeof(int)*3+2];
488 snprintf(sval, sizeof(sval), "%d", val);
489 return attr_set_str(setp, key, sval);
493 int main(int argc, char *argv[])
495 struct attrset *set = NULL;
497 attr_set_int(&set, "One", 1);
498 attr_set_int(&set, "Twelve", 12);
499 attr_set_int(&set, "Four", 4);
500 if (attr_find_int(set, "One") +
501 attr_find_int(set, "Twelve") +
502 attr_find_int(set, "Four")
504 printf("Didn't find One, Twelve, Four\n");
507 if (attr_find_int(set, "Three") != -1) {
508 printf("Surprisingly found Three\n");
515 void attr_free(struct attrset **setp safe)
517 struct attrset *set = *setp;
520 struct attrset *next = set->next;
527 * When attributes are attached to a section of text,
528 * we might want to split the text and so split the attributes.
530 * 1- 'trim' all attributes beyond a given key
531 * 2- copy attributes, subtracting some number from the offset.
532 * At offset '0', an empty value deletes the key.
535 void attr_trim(struct attrset **setp safe, int nkey)
540 _attr_find(&setp, "", &offset, nkey);
550 /* make a copy of 'set', keeping only attributes from 'nkey'
553 struct attrset *attr_copy_tail(struct attrset *set, int nkey)
555 struct attrset *newset = NULL;
557 for (; set ; set = set->next) {
559 for (i = 0; i < set->len; ) {
569 while (*k && *k != ' ')
574 attr_set_str_key(&newset, k, v, n);
581 /* make a copy of 'set' */
582 struct attrset *attr_copy(struct attrset *set)
584 struct attrset *newset, **ep = &newset;
586 for (; set ; set = set->next) {
587 struct attrset *n = newattr(NULL, set->size);
588 memcpy(n->attrs, set->attrs, set->len);
598 /* Collect the attributes in effect at a given pos and return
599 * a new set with the new alternate numeric prefix, or nothing if '-1'
601 struct attrset *attr_collect(struct attrset *set, unsigned int pos,
604 struct attrset *newset = NULL;
607 for (; set ; set = set->next) {
609 for (i = 0; i < set->len; ) {
616 n = strtoul(k, &e, 10);
620 /* FIXME shouldn't need to test 'e' */
621 while (e && *e == ' ')
624 snprintf(kbuf, 512, "%d %s", prefix, e);
630 attr_set_str(&newset, e, v);
637 #ifdef TEST_ATTR_TRIM
640 "1 Bold", "2 Bold", "5 Bold", "10 Bold",
641 "0 Colour", "3 Colour", "08 Colour", "12 Colour",
642 "2 Invis", "4 Invis", "6 Invis", "9 Invis",
645 int main(int argc, char *argv[])
648 struct attrset *set = NULL;
649 struct attrset *newset, *new2;
651 for (i = 0; i < sizeof(keys)/sizeof(keys[0]); i++)
652 attr_set_str(&set, keys[i], keys[i], -1);
654 newset = attr_copy_tail(set, 5);
656 new2 = attr_collect(newset, 9, 4);