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 | } |
172 | |