Fossil

Check-in [e3710ccd3a]
Login

Check-in [e3710ccd3a]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Decouple parsing and HTML-specific rendering. Add support for back references in the list of footnotes. WIP - inline and multiline footnotes are not yet implemented.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | markdown-footnotes
Files: files | file ages | folders
SHA3-256: e3710ccd3a5af6ad7b784f5ac42b78105f87932f85d598afe4b5daabfe905696
User & Date: george 2022-01-29 00:19:48.472
References
2022-02-01
20:12
Support multiline footnote definitions and inline footnotes via <tt>^[...]</tt> syntax (this syntax is not settled yet). Fix overall link support that was broken by [e3710ccd3a5a]. ... (check-in: 78b7846b8e user: george tags: markdown-footnotes)
Context
2022-01-30
17:08
Add an "eye-candy": if a footnote's mark is followed then the corresponding back-reference is highlighted, if a footnote's back-reference is followed then highlight the corresponding footnote's mark. ... (check-in: 50dcf92f85 user: george tags: markdown-footnotes)
2022-01-29
00:19
Decouple parsing and HTML-specific rendering. Add support for back references in the list of footnotes. WIP - inline and multiline footnotes are not yet implemented. ... (check-in: e3710ccd3a user: george tags: markdown-footnotes)
2022-01-27
19:45
Minor code clean-up of src/markdown.c: add a few 'const' specifiers, reduce the scope of temporary variables and simplify their names. ... (check-in: b9393a4e64 user: george tags: markdown-footnotes)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/default.css.
1668
1669
1670
1671
1672
1673
1674




1675
1676
1677
1678
1679
1680
1681
  font-family: monospace;
}
div.content  div.markdown > ol.footnotes {
  font-size: 90%;
}
div.content  div.markdown > ol.footnotes > li {
  margin-bottom: 0.5em;




}

/* Objects in the "desktoponly" class are invisible on mobile */
@media screen and (max-width: 600px) {
  .desktoponly {
    display: none;
  }







>
>
>
>







1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
  font-family: monospace;
}
div.content  div.markdown > ol.footnotes {
  font-size: 90%;
}
div.content  div.markdown > ol.footnotes > li {
  margin-bottom: 0.5em;
}
div.content  div.markdown > ol.footnotes > li > .footnote-backrefs {
  margin-right: 1em;
  font-weight: bold;
}

/* Objects in the "desktoponly" class are invisible on mobile */
@media screen and (max-width: 600px) {
  .desktoponly {
    display: none;
  }
Changes to src/markdown.c.
43
44
45
46
47
48
49

50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67


68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

84
85
86
87
88
89
90
};

/* mkd_renderer -- functions for rendering parsed data */
struct mkd_renderer {
  /* document level callbacks */
  void (*prolog)(struct Blob *ob, void *opaque);
  void (*epilog)(struct Blob *ob, void *opaque);


  /* block level callbacks - NULL skips the block */
  void (*blockcode)(struct Blob *ob, struct Blob *text, void *opaque);
  void (*blockquote)(struct Blob *ob, struct Blob *text, void *opaque);
  void (*blockhtml)(struct Blob *ob, struct Blob *text, void *opaque);
  void (*header)(struct Blob *ob, struct Blob *text,
            int level, void *opaque);
  void (*hrule)(struct Blob *ob, void *opaque);
  void (*list)(struct Blob *ob, struct Blob *text, int flags, void *opaque);
  void (*listitem)(struct Blob *ob, struct Blob *text,
            int flags, void *opaque);
  void (*paragraph)(struct Blob *ob, struct Blob *text, void *opaque);
  void (*table)(struct Blob *ob, struct Blob *head_row, struct Blob *rows,
              void *opaque);
  void (*table_cell)(struct Blob *ob, struct Blob *text, int flags,
              void *opaque);
  void (*table_row)(struct Blob *ob, struct Blob *cells, int flags,
              void *opaque);



  /* span level callbacks - NULL or return 0 prints the span verbatim */
  int (*autolink)(struct Blob *ob, struct Blob *link,
          enum mkd_autolink type, void *opaque);
  int (*codespan)(struct Blob *ob, struct Blob *text, int nSep, void *opaque);
  int (*double_emphasis)(struct Blob *ob, struct Blob *text,
            char c, void *opaque);
  int (*emphasis)(struct Blob *ob, struct Blob *text, char c,void*opaque);
  int (*image)(struct Blob *ob, struct Blob *link, struct Blob *title,
            struct Blob *alt, void *opaque);
  int (*linebreak)(struct Blob *ob, void *opaque);
  int (*link)(struct Blob *ob, struct Blob *link, struct Blob *title,
          struct Blob *content, void *opaque);
  int (*raw_html_tag)(struct Blob *ob, struct Blob *tag, void *opaque);
  int (*triple_emphasis)(struct Blob *ob, struct Blob *text,
            char c, void *opaque);


  /* low level callbacks - NULL copies input directly into the output */
  void (*entity)(struct Blob *ob, struct Blob *entity, void *opaque);
  void (*normal_text)(struct Blob *ob, struct Blob *text, void *opaque);

  /* renderer data */
  const char *emph_chars; /* chars that trigger emphasis rendering */







>


















>
>
















>







43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
};

/* mkd_renderer -- functions for rendering parsed data */
struct mkd_renderer {
  /* document level callbacks */
  void (*prolog)(struct Blob *ob, void *opaque);
  void (*epilog)(struct Blob *ob, void *opaque);
  void (*footnotes)(struct Blob *ob, const struct Blob *items, void *opaque);

  /* block level callbacks - NULL skips the block */
  void (*blockcode)(struct Blob *ob, struct Blob *text, void *opaque);
  void (*blockquote)(struct Blob *ob, struct Blob *text, void *opaque);
  void (*blockhtml)(struct Blob *ob, struct Blob *text, void *opaque);
  void (*header)(struct Blob *ob, struct Blob *text,
            int level, void *opaque);
  void (*hrule)(struct Blob *ob, void *opaque);
  void (*list)(struct Blob *ob, struct Blob *text, int flags, void *opaque);
  void (*listitem)(struct Blob *ob, struct Blob *text,
            int flags, void *opaque);
  void (*paragraph)(struct Blob *ob, struct Blob *text, void *opaque);
  void (*table)(struct Blob *ob, struct Blob *head_row, struct Blob *rows,
              void *opaque);
  void (*table_cell)(struct Blob *ob, struct Blob *text, int flags,
              void *opaque);
  void (*table_row)(struct Blob *ob, struct Blob *cells, int flags,
              void *opaque);
  void (*footnote_item)(struct Blob *ob, const struct Blob *text, 
              int index, int nUsed, void *opaque);

  /* span level callbacks - NULL or return 0 prints the span verbatim */
  int (*autolink)(struct Blob *ob, struct Blob *link,
          enum mkd_autolink type, void *opaque);
  int (*codespan)(struct Blob *ob, struct Blob *text, int nSep, void *opaque);
  int (*double_emphasis)(struct Blob *ob, struct Blob *text,
            char c, void *opaque);
  int (*emphasis)(struct Blob *ob, struct Blob *text, char c,void*opaque);
  int (*image)(struct Blob *ob, struct Blob *link, struct Blob *title,
            struct Blob *alt, void *opaque);
  int (*linebreak)(struct Blob *ob, void *opaque);
  int (*link)(struct Blob *ob, struct Blob *link, struct Blob *title,
          struct Blob *content, void *opaque);
  int (*raw_html_tag)(struct Blob *ob, struct Blob *tag, void *opaque);
  int (*triple_emphasis)(struct Blob *ob, struct Blob *text,
            char c, void *opaque);
  int (*footnote_ref)(struct Blob *ob, int index, int locus, void *opaque);

  /* low level callbacks - NULL copies input directly into the output */
  void (*entity)(struct Blob *ob, struct Blob *entity, void *opaque);
  void (*normal_text)(struct Blob *ob, struct Blob *text, void *opaque);

  /* renderer data */
  const char *emph_chars; /* chars that trigger emphasis rendering */
120
121
122
123
124
125
126


127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142

143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168


169
170
171
172
173
174
175
  struct Blob *ob,
  const struct Blob *ib,
  const struct mkd_renderer *rndr);


#endif /* INTERFACE */




/***************
 * LOCAL TYPES *
 ***************/

/* link_ref -- reference to a link */
struct link_ref {
  struct Blob id;     /* must be the first field as in footnote struct */
  struct Blob link;
  struct Blob title;
};

struct footnote {
  struct Blob id;   /* must be the first field as in link_ref struct  */
  struct Blob text; /* footnote's content that is rendered at the end */
  int    index;     /* serial number, in the order of appearance */

};


/* char_trigger -- function pointer to render active chars */
/*   returns the number of chars taken care of */
/*   data is the pointer of the beginning of the span */
/*   offset is the number of valid chars before data */
struct render;
typedef size_t (*char_trigger)(
  struct Blob *ob,
  struct render *rndr,
  char *data,
  size_t offset,
  size_t size);


/* render -- structure containing one particular render */
struct render {
  struct mkd_renderer make;
  struct Blob refs;
  struct Blob footnotes;
  char_trigger active_char[256];
  int iDepth;                    /* Depth of recursion */
  int nBlobCache;                /* Number of entries in aBlobCache */
  struct Blob *aBlobCache[20];   /* Cache of Blobs available for reuse */
  int    nLabels;                /* Footnotes counter for the second pass */


};

/* html_tag -- structure for quick HTML tag search (inspired from discount) */
struct html_tag {
  const char *text;
  int size;
};







>
>
















>




















<




|
>
>







124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169

170
171
172
173
174
175
176
177
178
179
180
181
182
183
  struct Blob *ob,
  const struct Blob *ib,
  const struct mkd_renderer *rndr);


#endif /* INTERFACE */

#define BLOB_COUNT(blob_ptr,el_type) (blob_size(blob_ptr)/sizeof(el_type))
#define COUNT_FOOTNOTES(blob_ptr) BLOB_COUNT(blob_ptr,struct footnote)

/***************
 * LOCAL TYPES *
 ***************/

/* link_ref -- reference to a link */
struct link_ref {
  struct Blob id;     /* must be the first field as in footnote struct */
  struct Blob link;
  struct Blob title;
};

struct footnote {
  struct Blob id;   /* must be the first field as in link_ref struct  */
  struct Blob text; /* footnote's content that is rendered at the end */
  int    index;     /* serial number, in the order of appearance */
  int    nUsed;     /* number of references to this note */
};


/* char_trigger -- function pointer to render active chars */
/*   returns the number of chars taken care of */
/*   data is the pointer of the beginning of the span */
/*   offset is the number of valid chars before data */
struct render;
typedef size_t (*char_trigger)(
  struct Blob *ob,
  struct render *rndr,
  char *data,
  size_t offset,
  size_t size);


/* render -- structure containing one particular render */
struct render {
  struct mkd_renderer make;
  struct Blob refs;

  char_trigger active_char[256];
  int iDepth;                    /* Depth of recursion */
  int nBlobCache;                /* Number of entries in aBlobCache */
  struct Blob *aBlobCache[20];   /* Cache of Blobs available for reuse */

  struct Blob notes;  /* array of footnotes */
  int iNotesCount;    /* count distinct indices found in the second pass */
};

/* html_tag -- structure for quick HTML tag search (inspired from discount) */
struct html_tag {
  const char *text;
  int size;
};
252
253
254
255
256
257
258
259

260
261
262
263


264
265
266
267
268
269
270
/* cmp_link_ref_sort -- comparison function for link_ref qsort */
static int cmp_link_ref_sort(const void *a, const void *b){
  struct link_ref *lra = (void *)a;
  struct link_ref *lrb = (void *)b;
  return blob_compare(&lra->id, &lrb->id);
}

/* cmp_footnote_sort -- comparison function for footnotes qsort */

static int cmp_footnote_sort(const void *a, const void *b){
  const struct footnote *fna = (void *)a, *fnb = (void *)b;
  assert( fna->index > 0 && fnb->index > 0 );
  if( fna->index == fnb->index ) return 0;


  return ( fna->index < fnb->index ? -1 : 1 );
}

/* cmp_html_tag -- comparison function for bsearch() (stolen from discount) */
static int cmp_html_tag(const void *a, const void *b){
  const struct html_tag *hta = a;
  const struct html_tag *htb = b;







|
>


|

>
>







260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
/* cmp_link_ref_sort -- comparison function for link_ref qsort */
static int cmp_link_ref_sort(const void *a, const void *b){
  struct link_ref *lra = (void *)a;
  struct link_ref *lrb = (void *)b;
  return blob_compare(&lra->id, &lrb->id);
}

/* cmp_footnote_sort -- comparison function for footnotes qsort.
 * Unused footnotes (when index == 0) sort last */
static int cmp_footnote_sort(const void *a, const void *b){
  const struct footnote *fna = (void *)a, *fnb = (void *)b;
  assert( fna->index >= 0 && fnb->index >= 0 );
  if( fna->index == fnb->index ) return 0;
  if( fna->index == 0 ) return  1;
  if( fnb->index == 0 ) return -1;
  return ( fna->index < fnb->index ? -1 : 1 );
}

/* cmp_html_tag -- comparison function for bsearch() (stolen from discount) */
static int cmp_html_tag(const void *a, const void *b){
  const struct html_tag *hta = a;
  const struct html_tag *htb = b;
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043

1044
1045

1046
1047

1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060


1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
  blob_reset(link);
  blob_reset(title);
  blob_append(link, blob_buffer(&lr->link), blob_size(&lr->link));
  blob_append(title, blob_buffer(&lr->title), blob_size(&lr->title));
  return 0;
}

/* get_footnote -- resolve footnote by its id
 * on success: fill text and return positive footnote's index
 * on failure: return -1 */
static int get_footnote(
  struct render *rndr,
  struct Blob *text,
  const char *data,
  size_t size
){
  struct footnote *fn;

  blob_reset(text);   /* use text for temporary storage */
  if( build_ref_id(text, data, size)<0 ) return -1;

  fn = bsearch(text,
               blob_buffer(&rndr->footnotes),
               blob_size(&rndr->footnotes)/sizeof(struct footnote),
               sizeof (struct footnote),
               cmp_link_ref);
  if( !fn ) return -1;

  if( fn->index == 0 ){  /* the first reference to the footnote */

    fn->index = ++(rndr->nLabels);
  }

  assert( fn->index > 0 );
  blob_reset(text);

  blob_append(text, blob_buffer(&fn->text), blob_size(&fn->text));
  return fn->index;
}

/* char_link -- '[': parsing a link or an image */
static size_t char_link(
  struct Blob *ob,
  struct render *rndr,
  char *data,
  size_t offset,
  size_t size
){
  int is_img = (offset && data[-1] == '!'), level;


  size_t i = 1, txt_e;
  struct Blob *content = 0;
  struct Blob *link = 0;
  struct Blob *title = 0;
  const int is_note = (size && data[1] == '^');
  int ret;

  /* checking whether the correct renderer exists */
  if( (is_img && !rndr->make.image) || (!is_img && !rndr->make.link) ){
    return 0;
  }

  /* looking for the matching closing bracket */







|


|

<



|
|
<
|
<
|
|
<


|


>
|

>

|
>
|
|










|
>
>




|
|







1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037

1038
1039
1040
1041
1042

1043

1044
1045

1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
  blob_reset(link);
  blob_reset(title);
  blob_append(link, blob_buffer(&lr->link), blob_size(&lr->link));
  blob_append(title, blob_buffer(&lr->title), blob_size(&lr->title));
  return 0;
}

/* get_footnote() is invoked during the second pass
 * on success: fill text and return positive footnote's index
 * on failure: return -1 */
static const struct footnote* get_footnote(
  struct render *rndr,

  const char *data,
  size_t size
){
  struct footnote *fn = NULL;
  struct Blob *id = new_work_buffer(rndr);

  if( build_ref_id(id, data, size)<0 ) goto cleanup;

  fn = bsearch(id, blob_buffer(&rndr->notes),
               COUNT_FOOTNOTES(&rndr->notes),

               sizeof (struct footnote),
               cmp_link_ref);
  if( !fn ) goto cleanup;

  if( fn->index == 0 ){  /* the first reference to the footnote */
    assert( fn->nUsed == 0 );
    fn->index = ++(rndr->iNotesCount);
  }
  fn->nUsed++;
  assert( fn->index > 0 );
  assert( fn->nUsed > 0 );
cleanup:
  release_work_buffer( rndr, id );
  return fn;
}

/* char_link -- '[': parsing a link or an image */
static size_t char_link(
  struct Blob *ob,
  struct render *rndr,
  char *data,
  size_t offset,
  size_t size
){
  const int is_img = (offset && data[-1] == '!');
  const int is_inline = (offset && data[-1]=='^');
  const int is_note = !is_img && (is_inline || (size>1 && data[1]=='^'));
  size_t i = 1, txt_e;
  struct Blob *content = 0;
  struct Blob *link = 0;
  struct Blob *title = 0;
  const struct footnote *fn = 0;
  int level, ret;

  /* checking whether the correct renderer exists */
  if( (is_img && !rndr->make.image) || (!is_img && !rndr->make.link) ){
    return 0;
  }

  /* looking for the matching closing bracket */
1133
1134
1135
1136
1137
1138
1139

1140
1141
1142

1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164




1165
1166
1167
1168
1169
1170
1171
      goto char_link_cleanup;
    }
    i = id_end+1;

  /* shortcut reference style link */
  }else{
    if( is_note ){

      const int lbl = get_footnote(rndr, link, data+1, txt_e-1);
      if( lbl <= 0 ){
        goto char_link_cleanup;

      }
      blob_reset(link);
      blob_appendf(link, "#footnote-%i", lbl);
      blob_appendf(content,"<sup class='footnote'>%i</sup>",lbl);
    }else if( get_link_ref(rndr, link, title, data+1, txt_e-1)<0 ){
      goto char_link_cleanup;
    }

    /* rewinding the whitespace */
    i = txt_e+1;
  }

  /* building content: img alt is escaped, link content is parsed */
  if( txt_e>1 ){
    if( is_img ) blob_append(content, data+1, txt_e-1);
    else if(!is_note) parse_inline(content, rndr, data+1, txt_e-1);
  }

  /* calling the relevant rendering function */
  if( is_img ){
    if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ) ob->nUsed--;
    ret = rndr->make.image(ob, link, title, content, rndr->make.opaque);




  }else{
    ret = rndr->make.link(ob, link, title, content, rndr->make.opaque);
  }

  /* cleanup */
char_link_cleanup:
  release_work_buffer(rndr, title);







>
|
|
<
>

|
<
<











|






>
>
>
>







1145
1146
1147
1148
1149
1150
1151
1152
1153
1154

1155
1156
1157


1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
      goto char_link_cleanup;
    }
    i = id_end+1;

  /* shortcut reference style link */
  }else{
    if( is_note ){
      if( is_inline ){
        //fn = put_footnote(rndr, data+1, txt_e-1);
      }else{

        fn = get_footnote(rndr, data+1, txt_e-1);
      }
      if( !fn ) goto char_link_cleanup;


    }else if( get_link_ref(rndr, link, title, data+1, txt_e-1)<0 ){
      goto char_link_cleanup;
    }

    /* rewinding the whitespace */
    i = txt_e+1;
  }

  /* building content: img alt is escaped, link content is parsed */
  if( txt_e>1 ){
    if( is_img ) blob_append(content, data+1, txt_e-1);
    else if(is_inline) parse_inline(content, rndr, data+1, txt_e-1);
  }

  /* calling the relevant rendering function */
  if( is_img ){
    if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ) ob->nUsed--;
    ret = rndr->make.image(ob, link, title, content, rndr->make.opaque);
  }else if(fn){
    if(rndr->make.footnote_ref){
      ret = rndr->make.footnote_ref(ob,fn->index,fn->nUsed,rndr->make.opaque);
    }
  }else{
    ret = rndr->make.link(ob, link, title, content, rndr->make.opaque);
  }

  /* cleanup */
char_link_cleanup:
  release_work_buffer(rndr, title);
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
  size_t *last,       /* last character of the link */
  struct Blob * footnotes
){
  size_t i = 0;
  size_t id_offset, id_end;
  size_t note_offset, note_end;
  size_t line_end;
  struct footnote fn = { empty_blob, empty_blob, 0 };

  /* footnote definition must start at the begining of a line */
  if( beg+4>=end ) return 0;
  i += beg;

  /* id part: anything but a newline between brackets */
  if( data[i]!='[' || data[i+1]!='^' ) return 0;







|







2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
  size_t *last,       /* last character of the link */
  struct Blob * footnotes
){
  size_t i = 0;
  size_t id_offset, id_end;
  size_t note_offset, note_end;
  size_t line_end;
  struct footnote fn = { empty_blob, empty_blob, 0, 0 };

  /* footnote definition must start at the begining of a line */
  if( beg+4>=end ) return 0;
  i += beg;

  /* id part: anything but a newline between brackets */
  if( data[i]!='[' || data[i+1]!='^' ) return 0;
2309
2310
2311
2312
2313
2314
2315
2316

2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
  const struct Blob *ib,             /* input blob in markdown */
  const struct mkd_renderer *rndrer  /* renderer descriptor (callbacks) */
){
  struct link_ref *lr;
  struct footnote *fn;
  size_t i, beg, end = 0;
  struct render rndr;
  Blob text = BLOB_INITIALIZER;      /* input after the first pass */


  /* filling the render structure */
  if( !rndrer ) return;
  rndr.make = *rndrer;
  rndr.nBlobCache = 0;
  rndr.iDepth = 0;
  rndr.refs = empty_blob;
  rndr.footnotes = empty_blob;
  rndr.nLabels = 0;
  for(i=0; i<256; i++) rndr.active_char[i] = 0;
  if( (rndr.make.emphasis
    || rndr.make.double_emphasis
    || rndr.make.triple_emphasis)
   && rndr.make.emph_chars
  ){
    for(i=0; rndr.make.emph_chars[i]; i++){







|
>






|
|
|







2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
  const struct Blob *ib,             /* input blob in markdown */
  const struct mkd_renderer *rndrer  /* renderer descriptor (callbacks) */
){
  struct link_ref *lr;
  struct footnote *fn;
  size_t i, beg, end = 0;
  struct render rndr;
  Blob text = BLOB_INITIALIZER;        /* input after the first pass  */
  int nLabeled;        /* number of footnotes found by the first pass */

  /* filling the render structure */
  if( !rndrer ) return;
  rndr.make = *rndrer;
  rndr.nBlobCache = 0;
  rndr.iDepth = 0;
  rndr.refs  = empty_blob;
  rndr.notes = empty_blob;
  rndr.iNotesCount = 0;
  for(i=0; i<256; i++) rndr.active_char[i] = 0;
  if( (rndr.make.emphasis
    || rndr.make.double_emphasis
    || rndr.make.triple_emphasis)
   && rndr.make.emph_chars
  ){
    for(i=0; rndr.make.emph_chars[i]; i++){
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
  /* first pass: iterate over lines looking for references,
   * copying everything else into "text" */
  beg = 0;
  for(const size_t size = blob_size(ib); beg<size ;){
    const char* const data = blob_buffer(ib);
    if( is_ref(data, beg, size, &end, &rndr.refs) ){
      beg = end;
    }else if( is_footnote(data, beg, size, &end, &rndr.footnotes) ){
      /* FIXME: fossil_print("\nfootnote found at %i\n", beg); */
      beg = end;
    }else{ /* skipping to the next line */
      end = beg;
      while( end<size && data[end]!='\n' && data[end]!='\r' ){
        end += 1;
      }







|







2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
  /* first pass: iterate over lines looking for references,
   * copying everything else into "text" */
  beg = 0;
  for(const size_t size = blob_size(ib); beg<size ;){
    const char* const data = blob_buffer(ib);
    if( is_ref(data, beg, size, &end, &rndr.refs) ){
      beg = end;
    }else if(is_footnote(data, beg, size, &end, &rndr.notes)){
      /* FIXME: fossil_print("\nfootnote found at %i\n", beg); */
      beg = end;
    }else{ /* skipping to the next line */
      end = beg;
      while( end<size && data[end]!='\n' && data[end]!='\r' ){
        end += 1;
      }
2371
2372
2373
2374
2375
2376
2377

2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404

2405
2406

2407
2408


2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422






2423
2424
2425
2426
  /* sorting the reference array */
  if( blob_size(&rndr.refs) ){
    qsort(blob_buffer(&rndr.refs),
          blob_size(&rndr.refs)/sizeof(struct link_ref),
          sizeof(struct link_ref),
          cmp_link_ref_sort);
  }

  /* sorting the footnotes array by id */
  if( blob_size(&rndr.footnotes) ){
    qsort(blob_buffer(&rndr.footnotes),
          blob_size(&rndr.footnotes)/sizeof(struct footnote),
          sizeof(struct footnote),
          cmp_link_ref_sort);
  }

  /* second pass: actual rendering */
  if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
  parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));

  /* sorting the footnotes array by index */
  if( blob_size(&rndr.footnotes) ){
    qsort(blob_buffer(&rndr.footnotes),
          blob_size(&rndr.footnotes)/sizeof(struct footnote),
          sizeof(struct footnote),
          cmp_footnote_sort);
  }
  /* FIXME: decouple parsing and HTML-specific rendering of footnotes */
  if( rndr.nLabels ){
    fn = (struct footnote *)blob_buffer(&rndr.footnotes);
    end = blob_size(&rndr.footnotes)/sizeof(struct footnote);
    blob_appendf(ob, "\n<ol class='footnotes'>\n");
    for(i=0; i<end; i++){
      if(fn[i].index == 0) continue;
      blob_appendf(ob, "<li id='footnote-%i'>\n  ", fn[i].index );

      parse_inline(ob,&rndr,blob_buffer(&fn[i].text),blob_size(&fn[i].text));
      blob_append(ob,"\n</li>\n",7);

    }
    blob_append(ob, "</ol>\n", 7);


  }
  if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);

  /* clean-up */
  assert( rndr.iDepth==0 );
  blob_reset(&text);
  lr = (struct link_ref *)blob_buffer(&rndr.refs);
  end = blob_size(&rndr.refs)/sizeof(struct link_ref);
  for(i=0; i<end; i++){
    blob_reset(&lr[i].id);
    blob_reset(&lr[i].link);
    blob_reset(&lr[i].title);
  }
  blob_reset(&rndr.refs);






  for(i=0; i<rndr.nBlobCache; i++){
    fossil_free(rndr.aBlobCache[i]);
  }
}







>

|
|
<
<







|
|
|
|
|
|
<
<
<
<
<
|
|
|
|
>
|
|
>

|
>
>














>
>
>
>
>
>




2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397


2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410





2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
  /* sorting the reference array */
  if( blob_size(&rndr.refs) ){
    qsort(blob_buffer(&rndr.refs),
          blob_size(&rndr.refs)/sizeof(struct link_ref),
          sizeof(struct link_ref),
          cmp_link_ref_sort);
  }
  nLabeled = COUNT_FOOTNOTES(&rndr.notes);
  /* sorting the footnotes array by id */
  if( nLabeled ){
    qsort(blob_buffer(&rndr.notes), nLabeled, sizeof(struct footnote),


          cmp_link_ref_sort);
  }

  /* second pass: actual rendering */
  if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
  parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));

  fn = (struct footnote*)blob_buffer(&rndr.notes);
  if(rndr.iNotesCount && rndr.make.footnote_item && rndr.make.footnotes){
    Blob * one_item  = new_work_buffer(&rndr);
    Blob * all_items = new_work_buffer(&rndr);
    qsort( fn, COUNT_FOOTNOTES(&rndr.notes), sizeof(struct footnote),
           cmp_footnote_sort /* sort footnotes by index */ );





    blob_reset( all_items );
    for(i=0; i<rndr.iNotesCount; i++){
      assert( fn[i].index == i+1 );

      blob_reset( one_item );
      parse_inline( one_item, &rndr, blob_buffer(&fn[i].text),
                    blob_size(&fn[i].text));
      rndr.make.footnote_item( all_items, one_item, i+1, fn[i].nUsed, rndr.make.opaque);
    }
    rndr.make.footnotes(ob, all_items, rndr.make.opaque );
    release_work_buffer( &rndr, one_item );
    release_work_buffer( &rndr, all_items );
  }
  if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);

  /* clean-up */
  assert( rndr.iDepth==0 );
  blob_reset(&text);
  lr = (struct link_ref *)blob_buffer(&rndr.refs);
  end = blob_size(&rndr.refs)/sizeof(struct link_ref);
  for(i=0; i<end; i++){
    blob_reset(&lr[i].id);
    blob_reset(&lr[i].link);
    blob_reset(&lr[i].title);
  }
  blob_reset(&rndr.refs);
  end = COUNT_FOOTNOTES(&rndr.notes);
  for(i=0; i<end; i++){
    blob_reset(&fn[i].id);
    blob_reset(&fn[i].text);
  }
  blob_reset(&rndr.notes);
  for(i=0; i<rndr.nBlobCache; i++){
    fossil_free(rndr.aBlobCache[i]);
  }
}
Changes to src/markdown_html.c.
27
28
29
30
31
32
33


34
35
36
37
38
39
40

41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
















61
62
63
64
65
66
67
void markdown_to_html(
  struct Blob *input_markdown,
  struct Blob *output_title,
  struct Blob *output_body);

#endif /* INTERFACE */



/*
** An instance of the following structure is passed through the
** "opaque" pointer.
*/
typedef struct MarkdownToHtml MarkdownToHtml;
struct MarkdownToHtml {
  Blob *output_title;     /* Store the title here */

};


/* INTER_BLOCK -- skip a line between block level elements */
#define INTER_BLOCK(ob) \
  do { if( blob_size(ob)>0 ) blob_append_char(ob, '\n'); } while (0)

/* BLOB_APPEND_LITERAL -- append a string literal to a blob */
#define BLOB_APPEND_LITERAL(blob, literal) \
  blob_append((blob), "" literal, (sizeof literal)-1)
  /*
   * The empty string in the second argument leads to a syntax error
   * when the macro is not used with a string literal. Unfortunately
   * the error is not overly explicit.
   */

/* BLOB_APPEND_BLOB -- append blob contents to another */
#define BLOB_APPEND_BLOB(dest, src) \
  blob_append((dest), blob_buffer(src), blob_size(src))


















/* HTML escapes
**
** html_escape() converts < to &lt;, > to &gt;, and & to &amp;.
** html_quote() goes further and converts " into &quot; and ' in &#39;.
*/
static void html_quote(struct Blob *ob, const char *data, size_t size){







>
>







>




















>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
void markdown_to_html(
  struct Blob *input_markdown,
  struct Blob *output_title,
  struct Blob *output_body);

#endif /* INTERFACE */

typedef union { uint64_t u; char c[8]; unsigned char b[8]; } bitfield64_t;

/*
** An instance of the following structure is passed through the
** "opaque" pointer.
*/
typedef struct MarkdownToHtml MarkdownToHtml;
struct MarkdownToHtml {
  Blob *output_title;     /* Store the title here */
  bitfield64_t unique;    /* Enables construction of unique #id elements */
};


/* INTER_BLOCK -- skip a line between block level elements */
#define INTER_BLOCK(ob) \
  do { if( blob_size(ob)>0 ) blob_append_char(ob, '\n'); } while (0)

/* BLOB_APPEND_LITERAL -- append a string literal to a blob */
#define BLOB_APPEND_LITERAL(blob, literal) \
  blob_append((blob), "" literal, (sizeof literal)-1)
  /*
   * The empty string in the second argument leads to a syntax error
   * when the macro is not used with a string literal. Unfortunately
   * the error is not overly explicit.
   */

/* BLOB_APPEND_BLOB -- append blob contents to another */
#define BLOB_APPEND_BLOB(dest, src) \
  blob_append((dest), blob_buffer(src), blob_size(src))

/* Converts an integer to a null-terminated base26 representation
 * Return empty string if that integer is negative.   */
static bitfield64_t to_base26(int i, int uppercase){
  bitfield64_t x;
  int j;
  x.u = 0;
  if( i >= 0 ){
    for(j=7; j >= 0; j--){
      x.b[j] = (unsigned char)(uppercase?'A':'a') + i%26;
      if( (i /= 26) == 0 ) break;
    }
    x.u >>= 8*j;
  }
  x.c[7] = 0;
  return x;
}

/* HTML escapes
**
** html_escape() converts < to &lt;, > to &gt;, and & to &amp;.
** html_quote() goes further and converts " into &quot; and ' in &#39;.
*/
static void html_quote(struct Blob *ob, const char *data, size_t size){
301
302
303
304
305
306
307






308





























































309
310
311
312
313
314
315
  void *opaque
){
  BLOB_APPEND_LITERAL(ob, "  <tr>\n");
  BLOB_APPEND_BLOB(ob, cells);
  BLOB_APPEND_LITERAL(ob, "  </tr>\n");
}






































































/* HTML span tags */

static int html_raw_html_tag(struct Blob *ob, struct Blob *text, void *opaque){
  blob_append(ob, blob_buffer(text), blob_size(text));
  return 1;
}







>
>
>
>
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
  void *opaque
){
  BLOB_APPEND_LITERAL(ob, "  <tr>\n");
  BLOB_APPEND_BLOB(ob, cells);
  BLOB_APPEND_LITERAL(ob, "  </tr>\n");
}

static int html_footnote_ref(
  struct Blob *ob, int index, int locus, void *opaque
){
  const struct MarkdownToHtml *ctx = (struct MarkdownToHtml*)opaque;
  const bitfield64_t l = to_base26(locus-1,0);
  char pos[32];

  /* expect BUGs if the following yields compiler warnings */
  memset(pos,0,32);
  sprintf(pos, "%s%i-%s", ctx->unique.c, index, l.c);

  BLOB_APPEND_LITERAL(ob,"<a class='noteref' href='#footnote-");
  blob_appendf(ob,"%s' id='noteref-%s'><sup>%i</sup></a>",
                   pos,            pos,   index);
  return 1;
}

/* Render a single item of the footnotes list.
 * Each backref gets a unique id to enable dynamic styling. */
static void html_footnote_item(
  struct Blob *ob, const struct Blob *text, int index, int nUsed, void *opaque
){
  const struct MarkdownToHtml *ctx = (struct MarkdownToHtml*)opaque;
  char pos[24];
  if( index <= 0 || nUsed < 0 || !text || !blob_size(text) ){
    return;
  }

  /* expect BUGs if the following yields compiler warnings */
  memset(pos,0,24);
  sprintf(pos, "%s%i", ctx->unique.c, index);

  blob_appendf(ob, "<li id='footnote-%s'>", pos);
  BLOB_APPEND_LITERAL(ob,"<sup class='footnote-backrefs'>");
  if( nUsed <= 1 ){
    blob_appendf(ob,"<a id='footnote-%s-a' "
                     "href='#noteref-%s-a'>^</a>", pos, pos);
  }else{
    int i;
    blob_append_char(ob, '^');
    for(i=0; i<nUsed && i<26; i++){
      const int c = i + (unsigned)'a';
      blob_appendf(ob," <a id='footnote-%s-%c'"
                       " href='#noteref-%s-%c'>%c</a>", pos,c, pos,c, c);
    }
    /* It's unlikely that so many backrefs will be usefull */
    /* but maybe for some machine generated documents... */
    for(; i<nUsed && i<676; i++){
      const bitfield64_t l = to_base26(i,0);
      blob_appendf(ob," <a id='footnote-%s-%s'"
                       " href='#noteref-%s-%s'>%s</a>",
                       pos,l.c, pos,l.c, l.c);
    }
    if( i < nUsed ) BLOB_APPEND_LITERAL(ob," &hellip;");
  }
  BLOB_APPEND_LITERAL(ob,"</sup>\n\t");
  BLOB_APPEND_BLOB(ob, text);
  BLOB_APPEND_LITERAL(ob, "\n</li>\n");
}
static void html_footnotes(
  struct Blob *ob, const struct Blob *items, void *opaque
){
  if( items && blob_size(items) ){
    BLOB_APPEND_LITERAL(ob, "<ol class='footnotes'>\n");
    BLOB_APPEND_BLOB(ob, items);
    BLOB_APPEND_LITERAL(ob, "</ol>\n");
  }
}

/* HTML span tags */

static int html_raw_html_tag(struct Blob *ob, struct Blob *text, void *opaque){
  blob_append(ob, blob_buffer(text), blob_size(text));
  return 1;
}
561
562
563
564
565
566
567

568
569
570
571
572
573
574
575
576
577
578
579
580

581
582
583
584
585
586
587
588
589
590
591

592
593
594
595
596
597
598
599
600

601
602
603

604
605
606
607
608
  struct Blob *output_title,     /* Put title here.  May be NULL */
  struct Blob *output_body       /* Put document body here. */
){
  struct mkd_renderer html_renderer = {
    /* prolog and epilog */
    html_prolog,
    html_epilog,


    /* block level elements */
    html_blockcode,
    html_blockquote,
    html_blockhtml,
    html_header,
    html_hrule,
    html_list,
    html_list_item,
    html_paragraph,
    html_table,
    html_table_cell,
    html_table_row,


    /* span level elements */
    html_autolink,
    html_codespan,
    html_double_emphasis,
    html_emphasis,
    html_image,
    html_linebreak,
    html_link,
    html_raw_html_tag,
    html_triple_emphasis,


    /* low level elements */
    0,    /* entity */
    html_normal_text,

    /* misc. parameters */
    "*_", /* emph_chars */
    0     /* opaque */
  };

  MarkdownToHtml context;
  memset(&context, 0, sizeof(context));
  context.output_title = output_title;

  html_renderer.opaque = &context;
  if( output_title ) blob_reset(output_title);
  blob_reset(output_body);
  markdown(output_body, input_markdown, &html_renderer);
}







>













>











>









>



>





647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
  struct Blob *output_title,     /* Put title here.  May be NULL */
  struct Blob *output_body       /* Put document body here. */
){
  struct mkd_renderer html_renderer = {
    /* prolog and epilog */
    html_prolog,
    html_epilog,
    html_footnotes,

    /* block level elements */
    html_blockcode,
    html_blockquote,
    html_blockhtml,
    html_header,
    html_hrule,
    html_list,
    html_list_item,
    html_paragraph,
    html_table,
    html_table_cell,
    html_table_row,
    html_footnote_item,

    /* span level elements */
    html_autolink,
    html_codespan,
    html_double_emphasis,
    html_emphasis,
    html_image,
    html_linebreak,
    html_link,
    html_raw_html_tag,
    html_triple_emphasis,
    html_footnote_ref,

    /* low level elements */
    0,    /* entity */
    html_normal_text,

    /* misc. parameters */
    "*_", /* emph_chars */
    0     /* opaque */
  };
  static int invocation = -1; /* no marker for the first document */
  MarkdownToHtml context;
  memset(&context, 0, sizeof(context));
  context.output_title = output_title;
  context.unique = to_base26(invocation++,1);
  html_renderer.opaque = &context;
  if( output_title ) blob_reset(output_title);
  blob_reset(output_body);
  markdown(output_body, input_markdown, &html_renderer);
}