Fossil

Check-in [544df852]
Login

Check-in [544df852]

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

Overview
Comment:If several footnotes are defined with the same label then join them into a single footnote. Text from each definition becomes an item in the list. This solution makes such situations noticable for the usual case (when this is an oversight) but also not obtrusive for the rare cases (when this is intentional). The list is provided with a special class to enable styling via skin customization.
This check-in is known to cause crash, see the forthcoming check-in.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | markdown-footnotes
Files: files | file ages | folders
SHA3-256: 544df852b2d9a1e8326b7792f5f9b6d1da7eb74768297c3b267352ffd1e59d5b
User & Date: george 2022-02-08 14:04:49
References
2022-02-09
19:29
Fix a bug in the blob_reserve() function that was introduced by [1243bf39996b8a]. The current mainline is not affected because this function is not used anywhere. However it was causing memory corruption on the 'markdown-footnotes' branch since it was employed in [544df852b2d9a1]. ... (check-in: 7283ae6e user: george tags: markdown-footnotes)
Context
2022-02-08
14:09
An attempt to fix a "double free crash" from the previous check-in. ... (check-in: 18c9d103 user: george tags: markdown-footnotes)
14:04
If several footnotes are defined with the same label then join them into a single footnote. Text from each definition becomes an item in the list. This solution makes such situations noticable for the usual case (when this is an oversight) but also not obtrusive for the rare cases (when this is intentional). The list is provided with a special class to enable styling via skin customization.
This check-in is known to cause crash, see the forthcoming check-in.
... (check-in: 544df852 user: george tags: markdown-footnotes)
13:39
Add const qualifier to the arguments of the blob_compare() function. ... (check-in: 2822b63b user: george tags: markdown-footnotes)
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/markdown.c.

178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
  int nBlobCache;                /* Number of entries in aBlobCache */
  struct Blob *aBlobCache[20];   /* Cache of Blobs available for reuse */

  struct {
    Blob all;    /* array of footnotes */
    int nLbled;  /* number of labeled footnotes found during the first pass */
    int nMarks;  /* counts distinct indices found during the second pass    */
    struct footnote misref; /* nUsed counts misreferences, index must be -1 */
  } notes;
};

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







|







178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
  int nBlobCache;                /* Number of entries in aBlobCache */
  struct Blob *aBlobCache[20];   /* Cache of Blobs available for reuse */

  struct {
    Blob all;    /* array of footnotes */
    int nLbled;  /* number of labeled footnotes found during the first pass */
    int nMarks;  /* counts distinct indices found during the second pass    */
    struct footnote misref; /* nUsed counts misreferences, iMark must be -1 */
  } notes;
};

/* html_tag -- structure for quick HTML tag search (inspired from discount) */
struct html_tag {
  const char *text;
  int size;
269
270
271
272
273
274
275


















276
277
278
279
280
281
282

/* 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.
 * Unreferenced footnotes (when nUsed == 0) sort last and
 * are sorted in the order of definition in the source */
static int cmp_footnote_sort(const void *fna, const void *fnb){
  const struct footnote *a = fna, *b = fnb;
  int i, j;







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







269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300

/* 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_id -- comparison function for footnotes qsort.
 * Empty IDs sort last (in undetermined order).
 * Equal IDs are sorted in the REVERSED order of definition in the source */
static int cmp_footnote_id(const void *fna, const void *fnb){
  const struct footnote *a = fna, *b = fnb;
  const int szA = blob_size(&a->id), szB = blob_size(&b->id);
  if( szA ){
    if( szB ){
      int cmp = blob_compare(&a->id, &b->id);
      if( cmp ) return cmp;
    }else return -1;
  }else return szB ? 1 : 0;
  /* ids are equal and non-empty */
  if( a->defno < b->defno ) return -1;
  if( a->defno > b->defno ) return  1;
  return 0; /* should never reach here */
}

/* cmp_footnote_sort -- comparison function for footnotes qsort.
 * Unreferenced footnotes (when nUsed == 0) sort last and
 * are sorted in the order of definition in the source */
static int cmp_footnote_sort(const void *fna, const void *fnb){
  const struct footnote *a = fna, *b = fnb;
  int i, j;
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
  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);







|
|
|







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() -- find a footnote by label, invoked during the 2nd pass.
 * On success returns a footnote (after incrementing its nUsed field),
 * otherwise returns NULL */
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);
1079
1080
1081
1082
1083
1084
1085

1086
1087
1088
1089
1090
1091
1092
  fn->nUsed++;
  assert( fn->iMark > 0 );
  assert( fn->nUsed > 0 );
cleanup:
  release_work_buffer( rndr, id );
  return fn;
}

/* Adds unlabeled footnote to the rndr.
 * If text is blank then returns 0,
 * otherwise returns the address of the added footnote. */
static inline const struct footnote* add_inline_footnote(
  struct render *rndr,
  const char *text,
  size_t size







>







1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
  fn->nUsed++;
  assert( fn->iMark > 0 );
  assert( fn->nUsed > 0 );
cleanup:
  release_work_buffer( rndr, id );
  return fn;
}

/* Adds unlabeled footnote to the rndr.
 * If text is blank then returns 0,
 * otherwise returns the address of the added footnote. */
static inline const struct footnote* add_inline_footnote(
  struct render *rndr,
  const char *text,
  size_t size
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552

2553
2554
2555
2556

2557















2558







2559






2560






2561






2562
2563
2564
2565
2566
2567
2568
          blob_append_char(&text, '\n');
        }
        end += 1;
      }
      beg = end;
    }
  }
  assert( rndr.notes.nMarks==0 );
  /* 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);
  }
  rndr.notes.nLbled = COUNT_FOOTNOTES(&rndr.notes.all);
  /* sorting the footnotes array by id */

  if( rndr.notes.nLbled ){
    fn = CAST_AS_FOOTNOTES(&rndr.notes.all);
    qsort(fn, rndr.notes.nLbled, sizeof(struct footnote),
          cmp_link_ref_sort);

    for(i=0; i<rndr.notes.nLbled; i++){















      fn[i].index = i;







    }






    /* FIXME: handle footnotes with duplicated labels */






  }







  /* 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));

  if( (blob_size(&rndr.notes.all) || rndr.notes.misref.nUsed) ){








|








|
>
|

|
|
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
|
>
>
>
>
>
>
|
>
>
>
>
>
>

>
>
>
>
>
>







2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
          blob_append_char(&text, '\n');
        }
        end += 1;
      }
      beg = end;
    }
  }

  /* 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);
  }
  rndr.notes.nLbled = COUNT_FOOTNOTES(&rndr.notes.all);

  /* sort footnotes by ID and join duplicates */
  if( rndr.notes.nLbled > 1 ){
    fn = CAST_AS_FOOTNOTES(&rndr.notes.all);
    qsort(fn, rndr.notes.nLbled, sizeof(struct footnote), cmp_footnote_id);

    /* concatenate footnotes with equal labels */
    for(i=0; i<rndr.notes.nLbled ;){
      struct footnote *x = fn + i;
      size_t j = i+1, k = blob_size(&x->text) + 64;
      while(j<rndr.notes.nLbled && !blob_compare(&x->id, &fn[j].id)){
        k += blob_size(&fn[j].text) + 10;
        j++;
      }
      if( i+1<j ){
        Blob tmp = empty_blob;
        blob_reserve(&tmp, k);
        blob_append_string(&tmp, "<ul class='footnote-joined'>\n");
        for(k=i; k<j; k++){
          struct footnote *y = fn + k;
          blob_append_string(&tmp, "<li>");
          blob_append(&tmp, blob_buffer(&y->text), blob_size(&y->text));
          blob_append_string(&tmp, "</li>\n");

          /* free memory buffer */
          blob_reset(&y->text);
          if( k!=i ){
            blob_reset(&y->id);
            /* invalidate redundant elements (this is optional) */
            memset(y,0,sizeof(struct footnote));
            y->index = y->defno = y->iMark = y->nUsed = -42;
          }
        }
        blob_append_string(&tmp, "</ul>\n");
        x->text = tmp;
      }
      i = j;
    }

    /* move redundant elements to the end of array and truncate/resize */
    qsort(fn, rndr.notes.nLbled, sizeof(struct footnote), cmp_footnote_id);
    i = rndr.notes.nLbled;
    while( i && !blob_size(&fn[i-1].id) ){ i--; }
    rndr.notes.nLbled = i;
    blob_truncate( &rndr.notes.all, i*sizeof(struct footnote) );
  }
  assert( COUNT_FOOTNOTES(&rndr.notes.all) == rndr.notes.nLbled );
  fn = CAST_AS_FOOTNOTES(&rndr.notes.all);
  for(i=0; i<rndr.notes.nLbled; i++){
    fn[i].index = i;
  }
  assert( rndr.notes.nMarks==0 );

  /* 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));

  if( (blob_size(&rndr.notes.all) || rndr.notes.misref.nUsed) ){

Changes to test/markdown-test3.md.

8
9
10
11
12
13
14


15
16
17
18
19
20
21
executable that incorporates the abovementioned branch.**[^1]

Developers are invited to add test cases here[^here].
It is suggested that the more simple is a test case the earlier it should
appear in this document.[^ if glitch occurs	]

[^lost3]: This note was defined at the begining of the document.



A footnote's label should be case insensitive[^ case INSENSITIVE ],
it is whitespace-savvy and can even contain newlines.[^ a
multiline
label]

A labeled footnote may be [referenced several times][^many-refs].







>
>







8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
executable that incorporates the abovementioned branch.**[^1]

Developers are invited to add test cases here[^here].
It is suggested that the more simple is a test case the earlier it should
appear in this document.[^ if glitch occurs	]

[^lost3]: This note was defined at the begining of the document.

[^duplicate]: This came from the begining of the document.

A footnote's label should be case insensitive[^ case INSENSITIVE ],
it is whitespace-savvy and can even contain newlines.[^ a
multiline
label]

A labeled footnote may be [referenced several times][^many-refs].
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
If [undefined label is used][^] then red "`misref`" is emited instead of
a numeric marker.[^ see it yourself ]
This can be overridden by the skin though.

The refenrence at the end of this sentence is the sole reason of
rendering of <s>`lost1` and</s> [lost2][^].




## Footnotes

[branch]: /timeline?r=markdown-footnotes&nowiki

[^ 1]:  Footnotes is a Fossil' extention of
        Markdown. Your other tools may have limited support for these.

[^here]: [History of test/markdown-test3.md](/finfo/test/markdown-test3.md)

[^if glitch occurs]:
        So that simple cases are processed even if
        a glitch happens for more tricky cases.

[^	CASE	 insensitive  	]: And also tolerate whitespaces.

[^ a multiline label ]: But at a footnote's definition it should still
    be written within square brackets
             on a single line.



[^many-refs]:
   Each letter on the left is a back-reference to the place of use.
   Highlighted back-reference indicates a place from which navigation
   occurred[^lost1].

[^lost1]: This note was defined at the end of the document.







>
>
>



















>







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
If [undefined label is used][^] then red "`misref`" is emited instead of
a numeric marker.[^ see it yourself ]
This can be overridden by the skin though.

The refenrence at the end of this sentence is the sole reason of
rendering of <s>`lost1` and</s> [lost2][^].

If several labeled footnote definitions have the same equal label then texts
from all these definitions are joined.[^duplicate]

## Footnotes

[branch]: /timeline?r=markdown-footnotes&nowiki

[^ 1]:  Footnotes is a Fossil' extention of
        Markdown. Your other tools may have limited support for these.

[^here]: [History of test/markdown-test3.md](/finfo/test/markdown-test3.md)

[^if glitch occurs]:
        So that simple cases are processed even if
        a glitch happens for more tricky cases.

[^	CASE	 insensitive  	]: And also tolerate whitespaces.

[^ a multiline label ]: But at a footnote's definition it should still
    be written within square brackets
             on a single line.

[^duplicate]: And that came from the end of the document.

[^many-refs]:
   Each letter on the left is a back-reference to the place of use.
   Highlighted back-reference indicates a place from which navigation
   occurred[^lost1].

[^lost1]: This note was defined at the end of the document.