2 * Copyright Neil Brown ©2015-2020 <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,
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 len = nkeylen + strlen(key) + 1 + strlen(val) + 1;
330 while (set == NULL || set->len + len > set->size) {
331 /* Need to re-alloc or alloc new */
332 if (!set || (offset == 0 && len + set->len > MAX_ATTR_SIZE)) {
333 /* Create a new set - which may exceed MAX_ATTR_SIZE */
334 struct attrset *new = newattr(NULL, len);
338 } else if (set->len + len <= MAX_ATTR_SIZE) {
339 /* Just make this block bigger */
340 set = newattr(set, set->len + len);
342 } else if (offset + len <= MAX_ATTR_SIZE) {
343 /* split following entries in separate block */
344 struct attrset *new = newattr(NULL, set->len - offset);
345 new->next = set->next;
347 new->len = set->len - offset;
349 memcpy(new->attrs, set->attrs + offset,
352 /* offset must be > 0.
353 * Split off following entries and try again there.
355 struct attrset *new = newattr(NULL,
358 new->next = set->next;
360 new->len = set->len - offset;
362 memcpy(new->attrs, set->attrs + offset,
369 memmove(set->attrs + offset + len, set->attrs + offset,
371 #pragma GCC diagnostic push
372 // GCC really doesn't like the games I"m playing here.
373 #pragma GCC diagnostic ignored "-Wstringop-overflow"
374 strcpy(set->attrs + offset, nkey);
375 strcpy(set->attrs + offset + nkeylen, key);
376 strcpy(set->attrs + offset + nkeylen + strlen(key) + 1, val);
377 #pragma GCC diagnostic pop
382 int attr_set_str(struct attrset **setp safe,
383 const char *key safe, const char *val)
385 return attr_set_str_key(setp, key, val, -1);
388 #if defined(TEST_ATTR_ADD_DEL) || defined(TEST_ATTR_TRIM)
389 void attr_dump(struct attrset *set)
391 printf("DUMP ATTRS:\n");
394 printf(" %d of %d:\n", set->len, set->size);
395 for (i = 0; i < set->len; ) {
396 printf(" %3d: \"%s\"", i, set->attrs + i);
397 i += strlen(set->attrs+i) + 1;
398 printf(" -> \"%s\"\n", set->attrs + i);
399 i += strlen(set->attrs+i) + 1;
403 printf("END DUMP\n");
407 #ifdef TEST_ATTR_ADD_DEL
408 enum act { Add, Remove, Find };
414 { Add, "Hello", "world"},
415 { Add, "05 Foo", "Bar" },
416 { Add, "1 Bold", "off" },
417 { Add, "9 Underline", "on" },
418 { Remove, "Hello", NULL },
419 { Find, "5 Foo", "Bar" },
420 { Add, "20 Thing", "Stuff" },
421 { Add, "01 Bold", "on" },
422 { Add, "1 StrikeThrough", "no" },
423 { Add, "2 StrikeThrough", "no" },
424 { Find, "1 StrikeThrough", "no" },
425 { Find, "5 Foo", "Bar" },
426 { Add, "1 Nextthing", "nonono" },
429 int main(int argc, char *argv[])
433 struct attrset *set = NULL;
434 for (i = 0; i < sizeof(actions)/sizeof(actions[0]); i++) {
435 struct action *a = &actions[i];
439 attr_set_str(&set, a->key, a->val); continue;
441 if (!attr_del(&set, a->key)) {
442 printf("Action %d: Remove %s: failed\n",
449 v = attr_find(set, a->key);
450 if (!v || strcmp(v, a->val) != 0) {
451 printf("Action %d: Find %s: Found %s\n",
465 /* Have versions that take and return numbers, '-1' for 'not found' */
466 int attr_find_int(struct attrset *set, const char *key safe)
468 const char *val = attr_find(set, key);
474 rv = strtoul(val, &end, 10);
475 if (!end || end == val || *end)
480 int attr_set_int(struct attrset **setp safe, const char *key safe, int val)
482 /* 3 digits per bytes, +1 for sign and +1 for trailing nul */
483 char sval[sizeof(int)*3+2];
485 snprintf(sval, sizeof(sval), "%d", val);
486 return attr_set_str(setp, key, sval);
490 int main(int argc, char *argv[])
492 struct attrset *set = NULL;
494 attr_set_int(&set, "One", 1);
495 attr_set_int(&set, "Twelve", 12);
496 attr_set_int(&set, "Four", 4);
497 if (attr_find_int(set, "One") +
498 attr_find_int(set, "Twelve") +
499 attr_find_int(set, "Four")
501 printf("Didn't find One, Twelve, Four\n");
504 if (attr_find_int(set, "Three") != -1) {
505 printf("Surprisingly found Three\n");
512 void attr_free(struct attrset **setp safe)
514 struct attrset *set = *setp;
517 struct attrset *next = set->next;
524 * When attributes are attached to a section of text,
525 * we might want to split the text and so split the attributes.
527 * 1- 'trim' all attributes beyond a given key
528 * 2- copy attributes, subtracting some number from the offset.
529 * At offset '0', an empty value deletes the key.
532 void attr_trim(struct attrset **setp safe, int nkey)
537 __attr_find(&setp, "", &offset, nkey);
547 /* make a copy of 'set', keeping only attributes from 'nkey'
550 struct attrset *attr_copy_tail(struct attrset *set, int nkey)
552 struct attrset *newset = NULL;
554 for (; set ; set = set->next) {
556 for (i = 0; i < set->len; ) {
566 while (*k && *k != ' ')
571 attr_set_str_key(&newset, k, v, n);
578 /* Collect the attributes in effect at a given pos and return
579 * a new set with the new alternate numeric prefix, or nothing if '-1'
581 struct attrset *attr_collect(struct attrset *set, unsigned int pos,
584 struct attrset *newset = NULL;
587 for (; set ; set = set->next) {
589 for (i = 0; i < set->len; ) {
596 n = strtoul(k, &e, 10);
600 /* FIXME shouldn't need to test 'e' */
601 while (e && *e == ' ')
604 snprintf(kbuf, 512, "%d %s", prefix, e);
610 attr_set_str(&newset, e, v);
617 #ifdef TEST_ATTR_TRIM
620 "1 Bold", "2 Bold", "5 Bold", "10 Bold",
621 "0 Colour", "3 Colour", "08 Colour", "12 Colour",
622 "2 Invis", "4 Invis", "6 Invis", "9 Invis",
625 int main(int argc, char *argv[])
628 struct attrset *set = NULL;
629 struct attrset *newset, *new2;
631 for (i = 0; i < sizeof(keys)/sizeof(keys[0]); i++)
632 attr_set_str(&set, keys[i], keys[i], -1);
634 newset = attr_copy_tail(set, 5);
636 new2 = attr_collect(newset, 9, 4);