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
38 unsigned short size, /* space allocated */
44 #if defined(TEST_ATTR_ADD_DEL)
45 #define MAX_ATTR_SIZE (64 - sizeof(struct attrset))
47 #define MAX_ATTR_SIZE (512 - sizeof(struct attrset))
50 static struct attrset *newattr(struct attrset *old, int size)
52 struct attrset *set = realloc(old, sizeof(struct attrset) + size);
61 /* attr_cmp just deals with bytes and ASCII digits, so it is
62 * not aware for wchars
64 static int getcmptok(char **ap)
82 /* Compare 'a' and 'b' treating strings of digits as numbers.
83 * If bnum >= 0, it is used as a leading number on 'b'.
85 static int attr_cmp(char *a, char *b, int bnum)
112 struct {char *a, *b; int result;} test[] = {
113 { "hello", "there", -1},
114 { "6hello", "10world", -1},
115 { "0005six", "5six", 0},
118 int main(int argc, char *argv[])
122 for (i = 0; i < sizeof(test)/sizeof(test[0]); i++) {
123 if (attr_cmp(test[i].a, test[i].b, -1) == test[i].result)
124 printf("%s <-> %s = %d OK\n",
125 test[i].a, test[i].b, test[i].result);
127 printf("%s <-> %s = %d, not %d\n",
128 test[i].a, test[i].b, attr_cmp(test[i].a,
139 int __attr_find(struct attrset ***setpp, char *key, int *offsetp, int keynum)
141 struct attrset **setp = *setpp;
142 struct attrset *set = *setp;
149 attr_cmp(set->next->attrs, key, keynum) <= 0) {
155 for (i = 0; i < set->len; ) {
156 int cmp = attr_cmp(set->attrs + i, key, keynum);
162 i += strlen(set->attrs + i) + 1;
163 i += strlen(set->attrs + i) + 1;
169 int attr_del(struct attrset **setp, char *key)
176 cmp = __attr_find(&setp, key, &offset, -1);
182 len = strlen(set->attrs + offset) + 1;
183 len += strlen(set->attrs + offset + len) + 1;
184 memmove(set->attrs + offset,
185 set->attrs + offset + len,
186 set->len - (offset + len));
195 char *attr_get_str(struct attrset *set, char *key, int keynum)
197 struct attrset **setp = &set;
199 int cmp = __attr_find(&setp, key, &offset, keynum);
204 offset += strlen(set->attrs + offset) + 1;
205 return set->attrs + offset;
208 char *attr_find(struct attrset *set, char *key)
210 return attr_get_str(set, key, -1);
213 int attr_set_str(struct attrset **setp, char *key, char *val, int keynum)
222 cmp = __attr_find(&setp, key, &offset, keynum);
225 /* Remove old value */
227 len = strlen(set->attrs + offset) + 1;
228 len += strlen(set->attrs + offset + len) + 1;
229 memmove(set->attrs + offset,
230 set->attrs + offset + len,
231 set->len - (offset + len));
238 snprintf(nkey, sizeof(nkey), "%d ", keynum);
239 nkeylen = strlen(nkey);
241 len = nkeylen + strlen(key) + 1 + strlen(val) + 1;
242 if (set == NULL || set->len + len > set->size) {
243 /* Need to re-alloc or alloc new */
245 set = newattr(NULL, len);
247 } else if (set->len + len <= MAX_ATTR_SIZE) {
248 /* Just make this block bigger */
249 set = newattr(set, set->len + len);
251 } else if (offset + len <= MAX_ATTR_SIZE) {
252 /* split following entries in separate block */
253 struct attrset *new = newattr(NULL, set->len - offset);
255 new->next = set->next;
257 new->len = set->len - offset;
259 memcpy(new->attrs, set->attrs + offset,
262 /* Split follow in entries and store new entry there */
263 struct attrset *new = newattr(NULL, set->len - offset + len);
265 new->next = set->next;
267 new->len = set->len - offset;
269 memcpy(new->attrs, set->attrs + offset,
275 memmove(set->attrs + offset + len, set->attrs + offset, set->len - offset);
276 strncpy(set->attrs + offset, nkey, nkeylen);
277 strcpy(set->attrs + offset + nkeylen, key);
278 strcpy(set->attrs + offset + nkeylen + strlen(key) + 1, val);
283 #if defined(TEST_ATTR_ADD_DEL) || defined(TEST_ATTR_TRIM)
284 void attr_dump(struct attrset *set)
286 printf("DUMP ATTRS:\n");
289 printf(" %d of %d:\n", set->len, set->size);
290 for (i = 0; i < set->len; ) {
291 printf(" %3d: \"%s\"", i, set->attrs + i);
292 i += strlen(set->attrs+i) + 1;
293 printf(" -> \"%s\"\n", set->attrs + i);
294 i += strlen(set->attrs+i) + 1;
298 printf("END DUMP\n");
302 #ifdef TEST_ATTR_ADD_DEL
303 enum act { Add, Remove, Find };
309 { Add, "Hello", "world"},
310 { Add, "05 Foo", "Bar" },
311 { Add, "1 Bold", "off" },
312 { Add, "9 Underline", "on" },
313 { Remove, "Hello", NULL },
314 { Find, "5 Foo", "Bar" },
315 { Add, "20 Thing", "Stuff" },
316 { Add, "01 Bold", "on" },
317 { Add, "1 StrikeThrough", "no" },
318 { Add, "2 StrikeThrough", "no" },
319 { Find, "1 StrikeThrough", "no" },
320 { Find, "5 Foo", "Bar" },
321 { Add, "1 Nextthing", "nonono" },
324 int main(int argc, char *argv[])
328 struct attrset *set = NULL;
329 for (i = 0; i < sizeof(actions)/sizeof(actions[0]); i++) {
330 struct action *a = &actions[i];
334 attr_set_str(&set, a->key, a->val, -1); continue;
336 if (attr_del(&set, a->key) == 0) {
337 printf("Action %d: Remove %s: failed\n",
344 v = attr_find(set, a->key);
345 if (!v || strcmp(v, a->val) != 0) {
346 printf("Action %d: Find %s: Found %s\n",
360 /* Have versions that take and return numbers, '-1' for 'not found' */
361 int attr_find_int(struct attrset *set, char *key)
363 char *val = attr_find(set, key);
369 rv = strtoul(val, &end, 10);
370 if (end == val || *end)
375 int attr_set_int(struct attrset **setp, char *key, int val)
379 sprintf(sval, "%d", val);
380 return attr_set_str(setp, key, sval, -1);
384 int main(int argc, char *argv[])
386 struct attrset *set = NULL;
388 attr_set_int(&set, "One", 1);
389 attr_set_int(&set, "Twelve", 12);
390 attr_set_int(&set, "Four", 4);
391 if (attr_find_int(set, "One") +
392 attr_find_int(set, "Twelve") +
393 attr_find_int(set, "Four")
395 printf("Didn't find One, Twelve, Four\n");
398 if (attr_find_int(set, "Three") != -1) {
399 printf("Surprisingly found Three\n");
406 void attr_free(struct attrset **setp)
408 struct attrset *set = *setp;
411 struct attrset *next = set->next;
418 * When attributes are attached to a section of text,
419 * we might want to split the text and so split the attributes.
421 * 1- 'trim' all attributes beyond a given key
422 * 2- copy attributes, subtracting some number from the offset.
423 * At offset '0', an empty value deletes the key.
426 void attr_trim(struct attrset **setp, int nkey)
432 sprintf(key, "%d", nkey);
433 __attr_find(&setp, key, &offset, 0);
443 struct attrset *attr_copy_tail(struct attrset *set, int nkey)
445 struct attrset *newset = NULL;
447 for (; set ; set = set->next) {
449 for (i = 0; i < set->len; ) {
458 if (n <= nkey && *v == '\0')
460 attr_set_str(&newset, k, v, nkey);
467 /* Collect the attributes in effect at a given pos and return
468 * a new set with the new alternate numeric prefix, or nothing if '-1'
470 struct attrset *attr_collect(struct attrset *set, unsigned int pos,
473 struct attrset *newset = NULL;
476 for (; set ; set = set->next) {
478 for (i = 0; i < set->len; ) {
485 n = strtoul(k, &e, 10);
492 snprintf(kbuf, 512, "%d %s", prefix, e);
497 attr_set_str(&newset, e, v, -1);
504 #ifdef TEST_ATTR_TRIM
507 "1 Bold", "2 Bold", "5 Bold", "10 Bold",
508 "0 Colour", "3 Colour", "08 Colour", "12 Colour",
509 "2 Invis", "4 Invis", "6 Invis", "9 Invis",
512 int main(int argc, char *argv[])
515 struct attrset *set = NULL;
516 struct attrset *newset, *new2;
518 for (i = 0; i < sizeof(keys)/sizeof(keys[0]); i++)
519 attr_set_str(&set, keys[i], keys[i], -1);
521 newset = attr_copy_tail(set, 5);
523 new2 = attr_collect(newset, 9, 4);