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)
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)
83 /* Compare 'a' and 'b' treating strings of digits as numbers.
84 * If bnum >= 0, it is used as a leading number on 'b'.
86 static int attr_cmp(char *a, char *b, int bnum)
113 struct {char *a, *b; int result;} test[] = {
114 { "hello", "there", -1},
115 { "6hello", "10world", -1},
116 { "0005six", "5six", 0},
119 int main(int argc, char *argv[])
123 for (i = 0; i < sizeof(test)/sizeof(test[0]); i++) {
124 if (attr_cmp(test[i].a, test[i].b, -1) == test[i].result)
125 printf("%s <-> %s = %d OK\n",
126 test[i].a, test[i].b, test[i].result);
128 printf("%s <-> %s = %d, not %d\n",
129 test[i].a, test[i].b, attr_cmp(test[i].a,
140 static int __attr_find(struct attrset ***setpp, char *key, int *offsetp, int keynum)
142 struct attrset **setp = *setpp;
143 struct attrset *set = *setp;
150 attr_cmp(set->next->attrs, key, keynum) <= 0) {
156 for (i = 0; i < set->len; ) {
157 int cmp = attr_cmp(set->attrs + i, key, keynum);
163 i += strlen(set->attrs + i) + 1;
164 i += strlen(set->attrs + i) + 1;
170 int attr_del(struct attrset **setp, char *key)
177 cmp = __attr_find(&setp, key, &offset, -1);
183 len = strlen(set->attrs + offset) + 1;
184 len += strlen(set->attrs + offset + len) + 1;
185 memmove(set->attrs + offset,
186 set->attrs + offset + len,
187 set->len - (offset + len));
196 char *attr_get_str(struct attrset *set, char *key, int keynum)
198 struct attrset **setp = &set;
200 int cmp = __attr_find(&setp, key, &offset, keynum);
205 offset += strlen(set->attrs + offset) + 1;
206 return set->attrs + offset;
209 char *attr_find(struct attrset *set, char *key)
211 return attr_get_str(set, key, -1);
214 int attr_set_str_key(struct attrset **setp, char *key, char *val, int keynum)
223 cmp = __attr_find(&setp, key, &offset, keynum);
226 /* Remove old value */
228 len = strlen(set->attrs + offset) + 1;
229 len += strlen(set->attrs + offset + len) + 1;
230 memmove(set->attrs + offset,
231 set->attrs + offset + len,
232 set->len - (offset + len));
239 snprintf(nkey, sizeof(nkey), "%d ", keynum);
240 nkeylen = strlen(nkey);
242 len = nkeylen + strlen(key) + 1 + strlen(val) + 1;
243 if (set == NULL || set->len + len > set->size) {
244 /* Need to re-alloc or alloc new */
246 set = newattr(NULL, len);
248 } else if (set->len + len <= MAX_ATTR_SIZE) {
249 /* Just make this block bigger */
250 set = newattr(set, set->len + len);
252 } else if (offset + len <= MAX_ATTR_SIZE) {
253 /* split following entries in separate block */
254 struct attrset *new = newattr(NULL, set->len - offset);
256 new->next = set->next;
258 new->len = set->len - offset;
260 memcpy(new->attrs, set->attrs + offset,
263 /* Split follow in entries and store new entry there */
264 struct attrset *new = newattr(NULL, set->len - offset + len);
266 new->next = set->next;
268 new->len = set->len - offset;
270 memcpy(new->attrs, set->attrs + offset,
276 memmove(set->attrs + offset + len, set->attrs + offset, set->len - offset);
277 strncpy(set->attrs + offset, nkey, nkeylen);
278 strcpy(set->attrs + offset + nkeylen, key);
279 strcpy(set->attrs + offset + nkeylen + strlen(key) + 1, val);
284 int attr_set_str(struct attrset **setp, char *key, char *val)
286 return attr_set_str_key(setp, key, val, -1);
289 #if defined(TEST_ATTR_ADD_DEL) || defined(TEST_ATTR_TRIM)
290 void attr_dump(struct attrset *set)
292 printf("DUMP ATTRS:\n");
295 printf(" %d of %d:\n", set->len, set->size);
296 for (i = 0; i < set->len; ) {
297 printf(" %3d: \"%s\"", i, set->attrs + i);
298 i += strlen(set->attrs+i) + 1;
299 printf(" -> \"%s\"\n", set->attrs + i);
300 i += strlen(set->attrs+i) + 1;
304 printf("END DUMP\n");
308 #ifdef TEST_ATTR_ADD_DEL
309 enum act { Add, Remove, Find };
315 { Add, "Hello", "world"},
316 { Add, "05 Foo", "Bar" },
317 { Add, "1 Bold", "off" },
318 { Add, "9 Underline", "on" },
319 { Remove, "Hello", NULL },
320 { Find, "5 Foo", "Bar" },
321 { Add, "20 Thing", "Stuff" },
322 { Add, "01 Bold", "on" },
323 { Add, "1 StrikeThrough", "no" },
324 { Add, "2 StrikeThrough", "no" },
325 { Find, "1 StrikeThrough", "no" },
326 { Find, "5 Foo", "Bar" },
327 { Add, "1 Nextthing", "nonono" },
330 int main(int argc, char *argv[])
334 struct attrset *set = NULL;
335 for (i = 0; i < sizeof(actions)/sizeof(actions[0]); i++) {
336 struct action *a = &actions[i];
340 attr_set_str(&set, a->key, a->val); continue;
342 if (attr_del(&set, a->key) == 0) {
343 printf("Action %d: Remove %s: failed\n",
350 v = attr_find(set, a->key);
351 if (!v || strcmp(v, a->val) != 0) {
352 printf("Action %d: Find %s: Found %s\n",
366 /* Have versions that take and return numbers, '-1' for 'not found' */
367 int attr_find_int(struct attrset *set, char *key)
369 char *val = attr_find(set, key);
375 rv = strtoul(val, &end, 10);
376 if (end == val || *end)
381 int attr_set_int(struct attrset **setp, char *key, int val)
385 sprintf(sval, "%d", val);
386 return attr_set_str(setp, key, sval);
390 int main(int argc, char *argv[])
392 struct attrset *set = NULL;
394 attr_set_int(&set, "One", 1);
395 attr_set_int(&set, "Twelve", 12);
396 attr_set_int(&set, "Four", 4);
397 if (attr_find_int(set, "One") +
398 attr_find_int(set, "Twelve") +
399 attr_find_int(set, "Four")
401 printf("Didn't find One, Twelve, Four\n");
404 if (attr_find_int(set, "Three") != -1) {
405 printf("Surprisingly found Three\n");
412 void attr_free(struct attrset **setp)
414 struct attrset *set = *setp;
417 struct attrset *next = set->next;
424 * When attributes are attached to a section of text,
425 * we might want to split the text and so split the attributes.
427 * 1- 'trim' all attributes beyond a given key
428 * 2- copy attributes, subtracting some number from the offset.
429 * At offset '0', an empty value deletes the key.
432 void attr_trim(struct attrset **setp, int nkey)
438 sprintf(key, "%d", nkey);
439 __attr_find(&setp, key, &offset, 0);
449 struct attrset *attr_copy_tail(struct attrset *set, int nkey)
451 struct attrset *newset = NULL;
453 for (; set ; set = set->next) {
455 for (i = 0; i < set->len; ) {
464 if (n <= nkey && *v == '\0')
466 attr_set_str_key(&newset, k, v, nkey);
473 /* Collect the attributes in effect at a given pos and return
474 * a new set with the new alternate numeric prefix, or nothing if '-1'
476 struct attrset *attr_collect(struct attrset *set, unsigned int pos,
479 struct attrset *newset = NULL;
482 for (; set ; set = set->next) {
484 for (i = 0; i < set->len; ) {
491 n = strtoul(k, &e, 10);
498 snprintf(kbuf, 512, "%d %s", prefix, e);
503 attr_set_str(&newset, e, v);
510 #ifdef TEST_ATTR_TRIM
513 "1 Bold", "2 Bold", "5 Bold", "10 Bold",
514 "0 Colour", "3 Colour", "08 Colour", "12 Colour",
515 "2 Invis", "4 Invis", "6 Invis", "9 Invis",
518 int main(int argc, char *argv[])
521 struct attrset *set = NULL;
522 struct attrset *newset, *new2;
524 for (i = 0; i < sizeof(keys)/sizeof(keys[0]); i++)
525 attr_set_str(&set, keys[i], keys[i], -1);
527 newset = attr_copy_tail(set, 5);
529 new2 = attr_collect(newset, 9, 4);