aboutsummaryrefslogtreecommitdiff
path: root/src/references.c
blob: c9051f096aacbe23066c5715dd7af7b7bc20d60f (plain)
  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. static unsigned int
  8. refhash(const unsigned char *link_ref)
  9. {
  10. unsigned int hash = 0;
  11. while (*link_ref)
  12. hash = (*link_ref++) + (hash << 6) + (hash << 16) - hash;
  13. return hash;
  14. }
  15. static void reference_free(cmark_reference *ref)
  16. {
  17. if(ref != NULL) {
  18. free(ref->label);
  19. free(ref->url);
  20. free(ref->title);
  21. free(ref);
  22. }
  23. }
  24. // normalize reference: collapse internal whitespace to single space,
  25. // remove leading/trailing whitespace, case fold
  26. // Return NULL if the reference name is actually empty (i.e. composed
  27. // solely from whitespace)
  28. static unsigned char *normalize_reference(cmark_chunk *ref)
  29. {
  30. cmark_strbuf normalized = GH_BUF_INIT;
  31. unsigned char *result;
  32. if(ref == NULL)
  33. return NULL;
  34. if (ref->len == 0)
  35. return NULL;
  36. utf8proc_case_fold(&normalized, ref->data, ref->len);
  37. cmark_strbuf_trim(&normalized);
  38. cmark_strbuf_normalize_whitespace(&normalized);
  39. result = cmark_strbuf_detach(&normalized);
  40. assert(result);
  41. if (result[0] == '\0') {
  42. free(result);
  43. return NULL;
  44. }
  45. return result;
  46. }
  47. static void add_reference(cmark_reference_map *map, cmark_reference* ref)
  48. {
  49. cmark_reference *t = ref->next = map->table[ref->hash % REFMAP_SIZE];
  50. while (t) {
  51. if (t->hash == ref->hash &&
  52. !strcmp((char *)t->label, (char *)ref->label)) {
  53. reference_free(ref);
  54. return;
  55. }
  56. t = t->next;
  57. }
  58. map->table[ref->hash % REFMAP_SIZE] = ref;
  59. }
  60. void cmark_reference_create(cmark_reference_map *map, cmark_chunk *label, cmark_chunk *url,
  61. cmark_chunk *title)
  62. {
  63. cmark_reference *ref;
  64. unsigned char *reflabel = normalize_reference(label);
  65. /* empty reference name, or composed from only whitespace */
  66. if (reflabel == NULL)
  67. return;
  68. ref = (cmark_reference *)calloc(1, sizeof(*ref));
  69. if(ref != NULL) {
  70. ref->label = reflabel;
  71. ref->hash = refhash(ref->label);
  72. ref->url = cmark_clean_url(url);
  73. ref->title = cmark_clean_title(title);
  74. ref->next = NULL;
  75. add_reference(map, ref);
  76. }
  77. }
  78. // Returns reference if refmap contains a reference with matching
  79. // label, otherwise NULL.
  80. cmark_reference* cmark_reference_lookup(cmark_reference_map *map, cmark_chunk *label)
  81. {
  82. cmark_reference *ref = NULL;
  83. unsigned char *norm;
  84. unsigned int hash;
  85. if (label->len > MAX_LINK_LABEL_LENGTH)
  86. return NULL;
  87. if (map == NULL)
  88. return NULL;
  89. norm = normalize_reference(label);
  90. if (norm == NULL)
  91. return NULL;
  92. hash = refhash(norm);
  93. ref = map->table[hash % REFMAP_SIZE];
  94. while (ref) {
  95. if (ref->hash == hash &&
  96. !strcmp((char *)ref->label, (char *)norm))
  97. break;
  98. ref = ref->next;
  99. }
  100. free(norm);
  101. return ref;
  102. }
  103. void cmark_reference_map_free(cmark_reference_map *map)
  104. {
  105. unsigned int i;
  106. if(map == NULL)
  107. return;
  108. for (i = 0; i < REFMAP_SIZE; ++i) {
  109. cmark_reference *ref = map->table[i];
  110. cmark_reference *next;
  111. while (ref) {
  112. next = ref->next;
  113. reference_free(ref);
  114. ref = next;
  115. }
  116. }
  117. free(map);
  118. }
  119. cmark_reference_map *cmark_reference_map_new(void)
  120. {
  121. return (cmark_reference_map *)calloc(1, sizeof(cmark_reference_map));
  122. }