Fossil

Check-in [7f6a6418]
Login

Check-in [7f6a6418]

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

Overview
Comment:Join duplicated footnotes slightly faster. Fix a comment about auxiliary cmp_footnote_id() function.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | markdown-footnotes
Files: files | file ages | folders
SHA3-256: 7f6a641808be00f8e68b5c23ce1ec8f3a1964ba53da68ed6076aa8d32fce6471
User & Date: george 2022-02-09 20:09:51
Context
2022-02-09
22:59
Handle unreferenced footnotes. If a labeled footnote is defined but there are no references to it, then add a special item at the end of footnotes. This item includes a label and the text of the strayed footnote - both rendered verbatim via html_escape(). Default skin makes such items visible and easily distinguishable. The order of such items match the order in the underlying source code. ... (check-in: ada55cd4 user: george tags: markdown-footnotes)
20:09
Join duplicated footnotes slightly faster. Fix a comment about auxiliary cmp_footnote_id() function. ... (check-in: 7f6a6418 user: george tags: markdown-footnotes)
19:38
Fix a misuse of an unsigned integer in the blobReallocMalloc() which can lead to redundant memory reallocations. ... (check-in: 92221aaa user: george tags: markdown-footnotes)
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/markdown.c.

272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
  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;







|







272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
  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 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;
2496
2497
2498
2499
2500
2501
2502

2503
2504
2505
2506
2507
2508
2509
  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;







>







2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
  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  */
  Blob *allNotes = &rndr.notes.all;

  /* filling the render structure */
  if( !rndrer ) return;
  rndr.make = *rndrer;
  rndr.nBlobCache = 0;
  rndr.iDepth = 0;
  rndr.refs  = empty_blob;
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
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
  /* 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_resize( &rndr.notes.all, i*sizeof(struct footnote) );

    /* FIXME: It was expected to work via truncation:
     *
     *    blob_truncate( &rndr.notes.all, i*sizeof(struct footnote) );
     *
     * but that way it crashes with
     *
     *    free(): double free detected in tcache 2
     *
     * This is strange. */
  }
  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) ){

    /* Footnotes must be parsed for the correct discovery of (back)links */
    Blob *notes = new_work_buffer( &rndr );
    Blob *tmp   = new_work_buffer( &rndr );
    const struct Blob *origin = &rndr.notes.all;
    int nMarks = -1;

    /* inline notes may get appended to rndr.notes.all while rendering */
    while(1){
      struct footnote *aNotes;
      const int N = COUNT_FOOTNOTES(origin);

      /* make a shallow copy of `origin` */
      blob_truncate(notes,0);
      blob_append(notes, blob_buffer(origin), blob_size(origin));
      aNotes = CAST_AS_FOOTNOTES(notes);
      qsort(aNotes, N, sizeof(struct footnote), cmp_footnote_sort);

      if( nMarks == rndr.notes.nMarks ) break;
      nMarks = rndr.notes.nMarks;

      for(i=0; i<N; i++){
        const int j = aNotes[i].index;
        struct footnote *x = CAST_AS_FOOTNOTES(origin) + j;
        assert( 0<=j && j<N );
        if( x->bRndred || !x->nUsed ) continue;
        assert( x->iMark > 0 );
        assert( blob_size(&x->text) );
        blob_truncate(tmp,0);

        /* `origin` may be altered and extended through this call */







|



>
|









>













<
|
<
<
<
<






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

<
|









|




<





|



|








|







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
2630
2631
2632
2633
2634
2635
2636

2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
  /* 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( allNotes );

  /* sort footnotes by ID and join duplicates */
  if( rndr.notes.nLbled > 1 ){
    int nDups = 0;
    fn = CAST_AS_FOOTNOTES( allNotes );
    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++;
        nDups++;
      }
      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);




        }
        blob_append_string(&tmp, "</ul>\n");
        x->text = tmp;
      }
      i = j;
    }
    if( nDups ){  /* clean rndr.notes.all from invalidated footnotes */
      const int n = rndr.notes.nLbled - nDups;
      struct Blob filtered = empty_blob;
      blob_reserve(&filtered, n*sizeof(struct footnote));
      for(i=0; i<rndr.notes.nLbled; i++){
        if( blob_size(&fn[i].id) ){
          blob_append(&filtered, (char*)(fn+i), sizeof(struct footnote));
        }
      }
      blob_reset( allNotes );
      rndr.notes.all = filtered;
      rndr.notes.nLbled = n;
      assert( COUNT_FOOTNOTES(allNotes) == rndr.notes.nLbled );
    }









  }

  fn = CAST_AS_FOOTNOTES( allNotes );
  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(allNotes) || rndr.notes.misref.nUsed) ){

    /* Footnotes must be parsed for the correct discovery of (back)links */
    Blob *notes = new_work_buffer( &rndr );
    Blob *tmp   = new_work_buffer( &rndr );

    int nMarks = -1;

    /* inline notes may get appended to rndr.notes.all while rendering */
    while(1){
      struct footnote *aNotes;
      const int N = COUNT_FOOTNOTES( allNotes );

      /* make a shallow copy of `origin` */
      blob_truncate(notes,0);
      blob_append(notes, blob_buffer(allNotes), blob_size(allNotes));
      aNotes = CAST_AS_FOOTNOTES(notes);
      qsort(aNotes, N, sizeof(struct footnote), cmp_footnote_sort);

      if( nMarks == rndr.notes.nMarks ) break;
      nMarks = rndr.notes.nMarks;

      for(i=0; i<N; i++){
        const int j = aNotes[i].index;
        struct footnote *x = CAST_AS_FOOTNOTES(allNotes) + j;
        assert( 0<=j && j<N );
        if( x->bRndred || !x->nUsed ) continue;
        assert( x->iMark > 0 );
        assert( blob_size(&x->text) );
        blob_truncate(tmp,0);

        /* `origin` may be altered and extended through this call */
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
  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);
  fn = CAST_AS_FOOTNOTES(&rndr.notes.all);
  end = COUNT_FOOTNOTES(&rndr.notes.all);
  for(i=0; i<end; i++){
    if(blob_size(&fn[i].id)) blob_reset(&fn[i].id);
    blob_reset(&fn[i].text);
  }
  blob_reset(&rndr.notes.all);
  for(i=0; i<rndr.nBlobCache; i++){
    fossil_free(rndr.aBlobCache[i]);
  }
}







|
|









2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
  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);
  fn = CAST_AS_FOOTNOTES( allNotes );
  end = COUNT_FOOTNOTES( allNotes );
  for(i=0; i<end; i++){
    if(blob_size(&fn[i].id)) blob_reset(&fn[i].id);
    blob_reset(&fn[i].text);
  }
  blob_reset(&rndr.notes.all);
  for(i=0; i<rndr.nBlobCache; i++){
    fossil_free(rndr.aBlobCache[i]);
  }
}