summaryrefslogtreecommitdiff
path: root/src/references.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/references.c')
-rw-r--r--src/references.c148
1 files changed, 148 insertions, 0 deletions
diff --git a/src/references.c b/src/references.c
new file mode 100644
index 0000000..04b9025
--- /dev/null
+++ b/src/references.c
@@ -0,0 +1,148 @@
+#include "cmark.h"
+#include "utf8.h"
+#include "references.h"
+#include "inlines.h"
+
+static unsigned int
+refhash(const unsigned char *link_ref)
+{
+ unsigned int hash = 0;
+
+ while (*link_ref)
+ hash = (*link_ref++) + (hash << 6) + (hash << 16) - hash;
+
+ return hash;
+}
+
+static void reference_free(reference *ref)
+{
+ if(ref != NULL) {
+ free(ref->label);
+ free(ref->url);
+ free(ref->title);
+ free(ref);
+ }
+}
+
+// normalize reference: collapse internal whitespace to single space,
+// remove leading/trailing whitespace, case fold
+// Return NULL if the reference name is actually empty (i.e. composed
+// solely from whitespace)
+static unsigned char *normalize_reference(chunk *ref)
+{
+ strbuf normalized = GH_BUF_INIT;
+ unsigned char *result;
+
+ if(ref == NULL)
+ return NULL;
+
+ if (ref->len == 0)
+ return NULL;
+
+ utf8proc_case_fold(&normalized, ref->data, ref->len);
+ strbuf_trim(&normalized);
+ strbuf_normalize_whitespace(&normalized);
+
+ result = strbuf_detach(&normalized);
+ assert(result);
+
+ if (result[0] == '\0') {
+ free(result);
+ return NULL;
+ }
+
+ return result;
+}
+
+static void add_reference(reference_map *map, reference* ref)
+{
+ reference *t = ref->next = map->table[ref->hash % REFMAP_SIZE];
+
+ while (t) {
+ if (t->hash == ref->hash &&
+ !strcmp((char *)t->label, (char *)ref->label)) {
+ reference_free(ref);
+ return;
+ }
+
+ t = t->next;
+ }
+
+ map->table[ref->hash % REFMAP_SIZE] = ref;
+}
+
+extern void reference_create(reference_map *map, chunk *label, chunk *url, chunk *title)
+{
+ reference *ref;
+ unsigned char *reflabel = normalize_reference(label);
+
+ /* empty reference name, or composed from only whitespace */
+ if (reflabel == NULL)
+ return;
+
+ ref = calloc(1, sizeof(*ref));
+ if(ref != NULL) {
+ ref->label = reflabel;
+ ref->hash = refhash(ref->label);
+ ref->url = clean_url(url);
+ ref->title = clean_title(title);
+ ref->next = NULL;
+
+ add_reference(map, ref);
+ }
+}
+
+// Returns reference if refmap contains a reference with matching
+// label, otherwise NULL.
+reference* reference_lookup(reference_map *map, chunk *label)
+{
+ reference *ref = NULL;
+ unsigned char *norm;
+ unsigned int hash;
+
+ if (map == NULL)
+ return NULL;
+
+ norm = normalize_reference(label);
+ if (norm == NULL)
+ return NULL;
+
+ hash = refhash(norm);
+ ref = map->table[hash % REFMAP_SIZE];
+
+ while (ref) {
+ if (ref->hash == hash &&
+ !strcmp((char *)ref->label, (char *)norm))
+ break;
+ ref = ref->next;
+ }
+
+ free(norm);
+ return ref;
+}
+
+void reference_map_free(reference_map *map)
+{
+ unsigned int i;
+
+ if(map == NULL)
+ return;
+
+ for (i = 0; i < REFMAP_SIZE; ++i) {
+ reference *ref = map->table[i];
+ reference *next;
+
+ while (ref) {
+ next = ref->next;
+ reference_free(ref);
+ ref = next;
+ }
+ }
+
+ free(map);
+}
+
+reference_map *reference_map_new(void)
+{
+ return calloc(1, sizeof(reference_map));
+}