2 * Copyright Neil Brown ©2015 <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 #if defined(TEST_ATTR_ADD_DEL)
46 #define MAX_ATTR_SIZE (64 - sizeof(struct attrset))
48 #define MAX_ATTR_SIZE (512 - sizeof(struct attrset))
51 static struct attrset *newattr(struct attrset *old, int size) safe
53 struct attrset *set = realloc(old, sizeof(struct attrset) + size);
62 /* attr_cmp just deals with bytes and ASCII digits, so it is
63 * not aware for wchars
65 static int getcmptok(char **ap safe)
72 /* FIXME smatch should handle "char * safe *ap safe" */
89 /* Compare 'a' and 'b' treating strings of digits as numbers.
90 * If bnum >= 0, it is used as a leading number on 'b'.
92 static int attr_cmp(char *a safe, char *b safe, int bnum)
119 struct {char *a, *b; int result;} test[] = {
120 { "hello", "there", -1},
121 { "6hello", "10world", -1},
122 { "0005six", "5six", 0},
125 int main(int argc, char *argv[])
129 for (i = 0; i < sizeof(test)/sizeof(test[0]); i++) {
130 if (attr_cmp(test[i].a, test[i].b, -1) == test[i].result)
131 printf("%s <-> %s = %d OK\n",
132 test[i].a, test[i].b, test[i].result);
134 printf("%s <-> %s = %d, not %d\n",
135 test[i].a, test[i].b, attr_cmp(test[i].a,
146 static int __attr_find(struct attrset ***setpp safe, char *key safe,
147 int *offsetp safe, int keynum)
149 struct attrset **setp safe;
154 /* FIXME smatch should check this */
162 attr_cmp(set->next->attrs, key, keynum) <= 0) {
168 for (i = 0; i < set->len; ) {
169 int cmp = attr_cmp(set->attrs + i, key, keynum);
175 i += strlen(set->attrs + i) + 1;
176 i += strlen(set->attrs + i) + 1;
182 int attr_del(struct attrset * *setp safe, char *key safe)
189 cmp = __attr_find(&setp, key, &offset, -1);
194 set = safe_cast *setp;
195 len = strlen(set->attrs + offset) + 1;
196 len += strlen(set->attrs + offset + len) + 1;
197 memmove(set->attrs + offset,
198 set->attrs + offset + len,
199 set->len - (offset + len));
208 char *attr_get_str(struct attrset *set, char *key safe, int keynum)
210 struct attrset **setp = &set;
212 int cmp = __attr_find(&setp, key, &offset, keynum);
214 if (cmp != 0 || !*setp)
217 offset += strlen(set->attrs + offset) + 1;
218 return set->attrs + offset;
221 char *attr_find(struct attrset *set, char *key safe)
223 return attr_get_str(set, key, -1);
226 char *attr_get_next_key(struct attrset *set, char *key safe, int keynum,
229 struct attrset **setp = &set;
231 int cmp = __attr_find(&setp, key, &offset, keynum);
236 set = safe_cast *setp;
238 /* Skip the matching key, then value */
239 offset += strlen(set->attrs + offset) + 1;
240 offset += strlen(set->attrs + offset) + 1;
242 if (offset >= set->len) {
248 key = set->attrs + offset;
249 val = key + strlen(key) + 1;
251 int kn = getcmptok(&key);
252 if (kn != keynum + 256)
261 int attr_set_str_key(struct attrset **setp safe, char *key safe, char *val, int keynum)
270 cmp = __attr_find(&setp, key, &offset, keynum);
273 /* Remove old value */
274 set = safe_cast *setp;
275 len = strlen(set->attrs + offset) + 1;
276 len += strlen(set->attrs + offset + len) + 1;
277 memmove(set->attrs + offset,
278 set->attrs + offset + len,
279 set->len - (offset + len));
286 snprintf(nkey, sizeof(nkey), "%d ", keynum);
287 nkeylen = strlen(nkey);
289 len = nkeylen + strlen(key) + 1 + strlen(val) + 1;
290 if (set == NULL || set->len + len > set->size) {
291 /* Need to re-alloc or alloc new */
293 set = newattr(NULL, len);
295 } else if (set->len + len <= MAX_ATTR_SIZE) {
296 /* Just make this block bigger */
297 set = newattr(set, set->len + len);
299 } else if (offset + len <= MAX_ATTR_SIZE) {
300 /* split following entries in separate block */
301 struct attrset *new = newattr(NULL, set->len - offset);
303 new->next = set->next;
305 new->len = set->len - offset;
307 memcpy(new->attrs, set->attrs + offset,
310 /* Split follow in entries and store new entry there */
311 struct attrset *new = newattr(NULL, set->len - offset + len);
313 new->next = set->next;
315 new->len = set->len - offset;
317 memcpy(new->attrs, set->attrs + offset,
323 memmove(set->attrs + offset + len, set->attrs + offset, set->len - offset);
324 strncpy(set->attrs + offset, nkey, nkeylen);
325 strcpy(set->attrs + offset + nkeylen, key);
326 strcpy(set->attrs + offset + nkeylen + strlen(key) + 1, val);
331 int attr_set_str(struct attrset **setp safe, char *key safe, char *val)
333 return attr_set_str_key(setp, key, val, -1);
336 #if defined(TEST_ATTR_ADD_DEL) || defined(TEST_ATTR_TRIM)
337 void attr_dump(struct attrset *set)
339 printf("DUMP ATTRS:\n");
342 printf(" %d of %d:\n", set->len, set->size);
343 for (i = 0; i < set->len; ) {
344 printf(" %3d: \"%s\"", i, set->attrs + i);
345 i += strlen(set->attrs+i) + 1;
346 printf(" -> \"%s\"\n", set->attrs + i);
347 i += strlen(set->attrs+i) + 1;
351 printf("END DUMP\n");
355 #ifdef TEST_ATTR_ADD_DEL
356 enum act { Add, Remove, Find };
362 { Add, "Hello", "world"},
363 { Add, "05 Foo", "Bar" },
364 { Add, "1 Bold", "off" },
365 { Add, "9 Underline", "on" },
366 { Remove, "Hello", NULL },
367 { Find, "5 Foo", "Bar" },
368 { Add, "20 Thing", "Stuff" },
369 { Add, "01 Bold", "on" },
370 { Add, "1 StrikeThrough", "no" },
371 { Add, "2 StrikeThrough", "no" },
372 { Find, "1 StrikeThrough", "no" },
373 { Find, "5 Foo", "Bar" },
374 { Add, "1 Nextthing", "nonono" },
377 int main(int argc, char *argv[])
381 struct attrset *set = NULL;
382 for (i = 0; i < sizeof(actions)/sizeof(actions[0]); i++) {
383 struct action *a = &actions[i];
387 attr_set_str(&set, a->key, a->val); continue;
389 if (attr_del(&set, a->key) == 0) {
390 printf("Action %d: Remove %s: failed\n",
397 v = attr_find(set, a->key);
398 if (!v || strcmp(v, a->val) != 0) {
399 printf("Action %d: Find %s: Found %s\n",
413 /* Have versions that take and return numbers, '-1' for 'not found' */
414 int attr_find_int(struct attrset *set, char *key safe)
416 char *val = attr_find(set, key);
422 rv = strtoul(val, &end, 10);
423 if (!end || end == val || *end)
428 int attr_set_int(struct attrset **setp safe, char *key safe, int val)
430 /* 3 digits per bytes, +1 for sign and +1 for trailing nul */
431 char sval[sizeof(int)*3+2];
433 snprintf(sval, sizeof(sval), "%d", val);
434 return attr_set_str(setp, key, sval);
438 int main(int argc, char *argv[])
440 struct attrset *set = NULL;
442 attr_set_int(&set, "One", 1);
443 attr_set_int(&set, "Twelve", 12);
444 attr_set_int(&set, "Four", 4);
445 if (attr_find_int(set, "One") +
446 attr_find_int(set, "Twelve") +
447 attr_find_int(set, "Four")
449 printf("Didn't find One, Twelve, Four\n");
452 if (attr_find_int(set, "Three") != -1) {
453 printf("Surprisingly found Three\n");
460 void attr_free(struct attrset **setp safe)
462 struct attrset *set = *setp;
465 struct attrset *next = set->next;
472 * When attributes are attached to a section of text,
473 * we might want to split the text and so split the attributes.
475 * 1- 'trim' all attributes beyond a given key
476 * 2- copy attributes, subtracting some number from the offset.
477 * At offset '0', an empty value deletes the key.
480 void attr_trim(struct attrset **setp safe, int nkey)
486 sprintf(key, "%d", nkey);
487 __attr_find(&setp, key, &offset, 0);
497 struct attrset *attr_copy_tail(struct attrset *set, int nkey)
499 struct attrset *newset = NULL;
501 for (; set ; set = set->next) {
503 for (i = 0; i < set->len; ) {
512 if (n <= nkey && *v == '\0')
514 attr_set_str_key(&newset, k, v, nkey);
521 /* Collect the attributes in effect at a given pos and return
522 * a new set with the new alternate numeric prefix, or nothing if '-1'
524 struct attrset *attr_collect(struct attrset *set, unsigned int pos,
527 struct attrset *newset = NULL;
530 for (; set ; set = set->next) {
532 for (i = 0; i < set->len; ) {
539 n = strtoul(k, &e, 10);
546 snprintf(kbuf, 512, "%d %s", prefix, e);
551 attr_set_str(&newset, e, v);
558 #ifdef TEST_ATTR_TRIM
561 "1 Bold", "2 Bold", "5 Bold", "10 Bold",
562 "0 Colour", "3 Colour", "08 Colour", "12 Colour",
563 "2 Invis", "4 Invis", "6 Invis", "9 Invis",
566 int main(int argc, char *argv[])
569 struct attrset *set = NULL;
570 struct attrset *newset, *new2;
572 for (i = 0; i < sizeof(keys)/sizeof(keys[0]); i++)
573 attr_set_str(&set, keys[i], keys[i], -1);
575 newset = attr_copy_tail(set, 5);
577 new2 = attr_collect(newset, 9, 4);