cmark
My personal build of CMark ✏️
references.c (4384B)
1 #include "cmark.h" 2 #include "utf8.h" 3 #include "parser.h" 4 #include "references.h" 5 #include "inlines.h" 6 #include "chunk.h" 7 8 static void reference_free(cmark_reference_map *map, cmark_reference *ref) { 9 cmark_mem *mem = map->mem; 10 if (ref != NULL) { 11 mem->free(ref->label); 12 mem->free(ref->url); 13 mem->free(ref->title); 14 mem->free(ref); 15 } 16 } 17 18 // normalize reference: collapse internal whitespace to single space, 19 // remove leading/trailing whitespace, case fold 20 // Return NULL if the reference name is actually empty (i.e. composed 21 // solely from whitespace) 22 static unsigned char *normalize_reference(cmark_mem *mem, cmark_chunk *ref) { 23 cmark_strbuf normalized = CMARK_BUF_INIT(mem); 24 unsigned char *result; 25 26 if (ref == NULL) 27 return NULL; 28 29 if (ref->len == 0) 30 return NULL; 31 32 cmark_utf8proc_case_fold(&normalized, ref->data, ref->len); 33 cmark_strbuf_trim(&normalized); 34 cmark_strbuf_normalize_whitespace(&normalized); 35 36 result = cmark_strbuf_detach(&normalized); 37 assert(result); 38 39 if (result[0] == '\0') { 40 mem->free(result); 41 return NULL; 42 } 43 44 return result; 45 } 46 47 void cmark_reference_create(cmark_reference_map *map, cmark_chunk *label, 48 cmark_chunk *url, cmark_chunk *title) { 49 cmark_reference *ref; 50 unsigned char *reflabel = normalize_reference(map->mem, label); 51 52 /* empty reference name, or composed from only whitespace */ 53 if (reflabel == NULL) 54 return; 55 56 assert(map->sorted == NULL); 57 58 ref = (cmark_reference *)map->mem->calloc(1, sizeof(*ref)); 59 ref->label = reflabel; 60 ref->url = cmark_clean_url(map->mem, url); 61 ref->title = cmark_clean_title(map->mem, title); 62 ref->age = map->size; 63 ref->next = map->refs; 64 65 if (ref->url != NULL) 66 ref->size += strlen((char*)ref->url); 67 if (ref->title != NULL) 68 ref->size += strlen((char*)ref->title); 69 70 map->refs = ref; 71 map->size++; 72 } 73 74 static int 75 labelcmp(const unsigned char *a, const unsigned char *b) { 76 return strcmp((const char *)a, (const char *)b); 77 } 78 79 static int 80 refcmp(const void *p1, const void *p2) { 81 cmark_reference *r1 = *(cmark_reference **)p1; 82 cmark_reference *r2 = *(cmark_reference **)p2; 83 int res = labelcmp(r1->label, r2->label); 84 return res ? res : ((int)r1->age - (int)r2->age); 85 } 86 87 static int 88 refsearch(const void *label, const void *p2) { 89 cmark_reference *ref = *(cmark_reference **)p2; 90 return labelcmp((const unsigned char *)label, ref->label); 91 } 92 93 static void sort_references(cmark_reference_map *map) { 94 unsigned int i = 0, last = 0, size = map->size; 95 cmark_reference *r = map->refs, **sorted = NULL; 96 97 sorted = (cmark_reference **)map->mem->calloc(size, sizeof(cmark_reference *)); 98 while (r) { 99 sorted[i++] = r; 100 r = r->next; 101 } 102 103 qsort(sorted, size, sizeof(cmark_reference *), refcmp); 104 105 for (i = 1; i < size; i++) { 106 if (labelcmp(sorted[i]->label, sorted[last]->label) != 0) 107 sorted[++last] = sorted[i]; 108 } 109 map->sorted = sorted; 110 map->size = last + 1; 111 } 112 113 // Returns reference if refmap contains a reference with matching 114 // label, otherwise NULL. 115 cmark_reference *cmark_reference_lookup(cmark_reference_map *map, 116 cmark_chunk *label) { 117 cmark_reference **ref = NULL; 118 cmark_reference *r = NULL; 119 unsigned char *norm; 120 121 if (label->len < 1 || label->len > MAX_LINK_LABEL_LENGTH) 122 return NULL; 123 124 if (map == NULL || !map->size) 125 return NULL; 126 127 norm = normalize_reference(map->mem, label); 128 if (norm == NULL) 129 return NULL; 130 131 if (!map->sorted) 132 sort_references(map); 133 134 ref = (cmark_reference **)bsearch(norm, map->sorted, map->size, sizeof(cmark_reference *), 135 refsearch); 136 map->mem->free(norm); 137 138 if (ref != NULL) { 139 r = ref[0]; 140 /* Check for expansion limit */ 141 if (map->max_ref_size && r->size > map->max_ref_size - map->ref_size) 142 return NULL; 143 map->ref_size += r->size; 144 } 145 146 return r; 147 } 148 149 void cmark_reference_map_free(cmark_reference_map *map) { 150 cmark_reference *ref; 151 152 if (map == NULL) 153 return; 154 155 ref = map->refs; 156 while (ref) { 157 cmark_reference *next = ref->next; 158 reference_free(map, ref); 159 ref = next; 160 } 161 162 map->mem->free(map->sorted); 163 map->mem->free(map); 164 } 165 166 cmark_reference_map *cmark_reference_map_new(cmark_mem *mem) { 167 cmark_reference_map *map = 168 (cmark_reference_map *)mem->calloc(1, sizeof(cmark_reference_map)); 169 map->mem = mem; 170 return map; 171 }