Fossil SCM

Extend [/md_rules#ftnts|Markdown] with footnotes support. See [/wiki?name=branch/markdown-footnotes#il|known limitations] and the corresponding [forum:/forumthread/ee1f1597e46ec07a|forum thread].

george 2022-04-23 21:32 trunk merge
Commit 3990518b296aa33f0e0bc898e5da4882e98cd252cdd89b1508b882a0b1a9a4fa
+10
--- Makefile.in
+++ Makefile.in
@@ -47,10 +47,17 @@
4747
CFLAGS = @CFLAGS@
4848
CFLAGS_INCLUDE = @CFLAGS_INCLUDE@
4949
LIB = @LDFLAGS@ @EXTRA_LDFLAGS@ @LIBS@
5050
BCCFLAGS = @CPPFLAGS@ $(CFLAGS)
5151
TCCFLAGS = @EXTRA_CFLAGS@ @CPPFLAGS@ $(CFLAGS) -DHAVE_AUTOCONFIG_H -D_HAVE_SQLITE_CONFIG_H
52
+#
53
+# Fuzzing may be enable by appending -fsanitize=fuzzer -DFOSSIL_FUZZ
54
+# to the TCCFLAGS variable.
55
+# For more thorouth (but also slower) investigation
56
+# -fsanitize=fuzzer,undefined,address
57
+# might be more useful.
58
+
5259
INSTALLDIR = $(DESTDIR)@prefix@/bin
5360
USE_SYSTEM_SQLITE = @USE_SYSTEM_SQLITE@
5461
SQLITE3_SRC.2 = @SQLITE3_SRC.2@
5562
SQLITE3_OBJ.2 = @SQLITE3_OBJ.2@
5663
SQLITE3_SHELL_SRC.2 = @SQLITE3_SHELL_SRC.2@
@@ -62,10 +69,13 @@
6269
# 0=src/shell.c, 1=src/shell-see.c, 2=$(SQLITE3_SHELL_SRC.2)
6370
USE_LINENOISE = @USE_LINENOISE@
6471
USE_MMAN_H = @USE_MMAN_H@
6572
USE_SEE = @USE_SEE@
6673
APPNAME = fossil
74
+#
75
+# APPNAME = fossil-fuzz
76
+# may be more appropriate for fuzzing.
6777
6878
.PHONY: all tags
6979
7080
include $(SRCDIR)/main.mk
7181
7282
--- Makefile.in
+++ Makefile.in
@@ -47,10 +47,17 @@
47 CFLAGS = @CFLAGS@
48 CFLAGS_INCLUDE = @CFLAGS_INCLUDE@
49 LIB = @LDFLAGS@ @EXTRA_LDFLAGS@ @LIBS@
50 BCCFLAGS = @CPPFLAGS@ $(CFLAGS)
51 TCCFLAGS = @EXTRA_CFLAGS@ @CPPFLAGS@ $(CFLAGS) -DHAVE_AUTOCONFIG_H -D_HAVE_SQLITE_CONFIG_H
 
 
 
 
 
 
 
52 INSTALLDIR = $(DESTDIR)@prefix@/bin
53 USE_SYSTEM_SQLITE = @USE_SYSTEM_SQLITE@
54 SQLITE3_SRC.2 = @SQLITE3_SRC.2@
55 SQLITE3_OBJ.2 = @SQLITE3_OBJ.2@
56 SQLITE3_SHELL_SRC.2 = @SQLITE3_SHELL_SRC.2@
@@ -62,10 +69,13 @@
62 # 0=src/shell.c, 1=src/shell-see.c, 2=$(SQLITE3_SHELL_SRC.2)
63 USE_LINENOISE = @USE_LINENOISE@
64 USE_MMAN_H = @USE_MMAN_H@
65 USE_SEE = @USE_SEE@
66 APPNAME = fossil
 
 
 
67
68 .PHONY: all tags
69
70 include $(SRCDIR)/main.mk
71
72
--- Makefile.in
+++ Makefile.in
@@ -47,10 +47,17 @@
47 CFLAGS = @CFLAGS@
48 CFLAGS_INCLUDE = @CFLAGS_INCLUDE@
49 LIB = @LDFLAGS@ @EXTRA_LDFLAGS@ @LIBS@
50 BCCFLAGS = @CPPFLAGS@ $(CFLAGS)
51 TCCFLAGS = @EXTRA_CFLAGS@ @CPPFLAGS@ $(CFLAGS) -DHAVE_AUTOCONFIG_H -D_HAVE_SQLITE_CONFIG_H
52 #
53 # Fuzzing may be enable by appending -fsanitize=fuzzer -DFOSSIL_FUZZ
54 # to the TCCFLAGS variable.
55 # For more thorouth (but also slower) investigation
56 # -fsanitize=fuzzer,undefined,address
57 # might be more useful.
58
59 INSTALLDIR = $(DESTDIR)@prefix@/bin
60 USE_SYSTEM_SQLITE = @USE_SYSTEM_SQLITE@
61 SQLITE3_SRC.2 = @SQLITE3_SRC.2@
62 SQLITE3_OBJ.2 = @SQLITE3_OBJ.2@
63 SQLITE3_SHELL_SRC.2 = @SQLITE3_SHELL_SRC.2@
@@ -62,10 +69,13 @@
69 # 0=src/shell.c, 1=src/shell-see.c, 2=$(SQLITE3_SHELL_SRC.2)
70 USE_LINENOISE = @USE_LINENOISE@
71 USE_MMAN_H = @USE_MMAN_H@
72 USE_SEE = @USE_SEE@
73 APPNAME = fossil
74 #
75 # APPNAME = fossil-fuzz
76 # may be more appropriate for fuzzing.
77
78 .PHONY: all tags
79
80 include $(SRCDIR)/main.mk
81
82
--- src/backlink.c
+++ src/backlink.c
@@ -255,10 +255,12 @@
255255
Backlink *p
256256
){
257257
struct mkd_renderer html_renderer = {
258258
/* prolog */ (void(*)(Blob*,void*))mkdn_noop0,
259259
/* epilog */ (void(*)(Blob*,void*))mkdn_noop0,
260
+ /* footnotes */ (void(*)(Blob*,const Blob*, void*))mkdn_noop0,
261
+
260262
/* blockcode */ (void(*)(Blob*,Blob*,void*))mkdn_noop0,
261263
/* blockquote */ (void(*)(Blob*,Blob*,void*))mkdn_noop0,
262264
/* blockhtml */ (void(*)(Blob*,Blob*,void*))mkdn_noop0,
263265
/* header */ (void(*)(Blob*,Blob*,int,void*))mkdn_noop0,
264266
/* hrule */ (void(*)(Blob*,void*))mkdn_noop0,
@@ -266,19 +268,23 @@
266268
/* listitem */ (void(*)(Blob*,Blob*,int,void*))mkdn_noop0,
267269
/* paragraph */ (void(*)(Blob*,Blob*,void*))mkdn_noop0,
268270
/* table */ (void(*)(Blob*,Blob*,Blob*,void*))mkdn_noop0,
269271
/* table_cell */ (void(*)(Blob*,Blob*,int,void*))mkdn_noop0,
270272
/* table_row */ (void(*)(Blob*,Blob*,int,void*))mkdn_noop0,
273
+ /* footnoteitm*/ (void(*)(Blob*,const Blob*,int,int,void*))mkdn_noop0,
274
+
271275
/* autolink */ (int(*)(Blob*,Blob*,enum mkd_autolink,void*))mkdn_noop1,
272276
/* codespan */ (int(*)(Blob*,Blob*,int,void*))mkdn_noop1,
273277
/* dbl_emphas */ (int(*)(Blob*,Blob*,char,void*))mkdn_noop1,
274278
/* emphasis */ (int(*)(Blob*,Blob*,char,void*))mkdn_noop1,
275279
/* image */ (int(*)(Blob*,Blob*,Blob*,Blob*,void*))mkdn_noop1,
276280
/* linebreak */ (int(*)(Blob*,void*))mkdn_noop1,
277281
/* link */ backlink_md_link,
278282
/* r_html_tag */ (int(*)(Blob*,Blob*,void*))mkdn_noop1,
279283
/* tri_emphas */ (int(*)(Blob*,Blob*,char,void*))mkdn_noop1,
284
+ /* footnoteref*/ (int(*)(Blob*,const Blob*,const Blob*,int,int,void*))mkdn_noop1,
285
+
280286
0, /* entity */
281287
0, /* normal_text */
282288
"*_", /* emphasis characters */
283289
0 /* client data */
284290
};
285291
--- src/backlink.c
+++ src/backlink.c
@@ -255,10 +255,12 @@
255 Backlink *p
256 ){
257 struct mkd_renderer html_renderer = {
258 /* prolog */ (void(*)(Blob*,void*))mkdn_noop0,
259 /* epilog */ (void(*)(Blob*,void*))mkdn_noop0,
 
 
260 /* blockcode */ (void(*)(Blob*,Blob*,void*))mkdn_noop0,
261 /* blockquote */ (void(*)(Blob*,Blob*,void*))mkdn_noop0,
262 /* blockhtml */ (void(*)(Blob*,Blob*,void*))mkdn_noop0,
263 /* header */ (void(*)(Blob*,Blob*,int,void*))mkdn_noop0,
264 /* hrule */ (void(*)(Blob*,void*))mkdn_noop0,
@@ -266,19 +268,23 @@
266 /* listitem */ (void(*)(Blob*,Blob*,int,void*))mkdn_noop0,
267 /* paragraph */ (void(*)(Blob*,Blob*,void*))mkdn_noop0,
268 /* table */ (void(*)(Blob*,Blob*,Blob*,void*))mkdn_noop0,
269 /* table_cell */ (void(*)(Blob*,Blob*,int,void*))mkdn_noop0,
270 /* table_row */ (void(*)(Blob*,Blob*,int,void*))mkdn_noop0,
 
 
271 /* autolink */ (int(*)(Blob*,Blob*,enum mkd_autolink,void*))mkdn_noop1,
272 /* codespan */ (int(*)(Blob*,Blob*,int,void*))mkdn_noop1,
273 /* dbl_emphas */ (int(*)(Blob*,Blob*,char,void*))mkdn_noop1,
274 /* emphasis */ (int(*)(Blob*,Blob*,char,void*))mkdn_noop1,
275 /* image */ (int(*)(Blob*,Blob*,Blob*,Blob*,void*))mkdn_noop1,
276 /* linebreak */ (int(*)(Blob*,void*))mkdn_noop1,
277 /* link */ backlink_md_link,
278 /* r_html_tag */ (int(*)(Blob*,Blob*,void*))mkdn_noop1,
279 /* tri_emphas */ (int(*)(Blob*,Blob*,char,void*))mkdn_noop1,
 
 
280 0, /* entity */
281 0, /* normal_text */
282 "*_", /* emphasis characters */
283 0 /* client data */
284 };
285
--- src/backlink.c
+++ src/backlink.c
@@ -255,10 +255,12 @@
255 Backlink *p
256 ){
257 struct mkd_renderer html_renderer = {
258 /* prolog */ (void(*)(Blob*,void*))mkdn_noop0,
259 /* epilog */ (void(*)(Blob*,void*))mkdn_noop0,
260 /* footnotes */ (void(*)(Blob*,const Blob*, void*))mkdn_noop0,
261
262 /* blockcode */ (void(*)(Blob*,Blob*,void*))mkdn_noop0,
263 /* blockquote */ (void(*)(Blob*,Blob*,void*))mkdn_noop0,
264 /* blockhtml */ (void(*)(Blob*,Blob*,void*))mkdn_noop0,
265 /* header */ (void(*)(Blob*,Blob*,int,void*))mkdn_noop0,
266 /* hrule */ (void(*)(Blob*,void*))mkdn_noop0,
@@ -266,19 +268,23 @@
268 /* listitem */ (void(*)(Blob*,Blob*,int,void*))mkdn_noop0,
269 /* paragraph */ (void(*)(Blob*,Blob*,void*))mkdn_noop0,
270 /* table */ (void(*)(Blob*,Blob*,Blob*,void*))mkdn_noop0,
271 /* table_cell */ (void(*)(Blob*,Blob*,int,void*))mkdn_noop0,
272 /* table_row */ (void(*)(Blob*,Blob*,int,void*))mkdn_noop0,
273 /* footnoteitm*/ (void(*)(Blob*,const Blob*,int,int,void*))mkdn_noop0,
274
275 /* autolink */ (int(*)(Blob*,Blob*,enum mkd_autolink,void*))mkdn_noop1,
276 /* codespan */ (int(*)(Blob*,Blob*,int,void*))mkdn_noop1,
277 /* dbl_emphas */ (int(*)(Blob*,Blob*,char,void*))mkdn_noop1,
278 /* emphasis */ (int(*)(Blob*,Blob*,char,void*))mkdn_noop1,
279 /* image */ (int(*)(Blob*,Blob*,Blob*,Blob*,void*))mkdn_noop1,
280 /* linebreak */ (int(*)(Blob*,void*))mkdn_noop1,
281 /* link */ backlink_md_link,
282 /* r_html_tag */ (int(*)(Blob*,Blob*,void*))mkdn_noop1,
283 /* tri_emphas */ (int(*)(Blob*,Blob*,char,void*))mkdn_noop1,
284 /* footnoteref*/ (int(*)(Blob*,const Blob*,const Blob*,int,int,void*))mkdn_noop1,
285
286 0, /* entity */
287 0, /* normal_text */
288 "*_", /* emphasis characters */
289 0 /* client data */
290 };
291
+24 -8
--- src/blob.c
+++ src/blob.c
@@ -53,10 +53,33 @@
5353
/*
5454
** The buffer holding the blob data
5555
*/
5656
#define blob_buffer(X) ((X)->aData)
5757
58
+/*
59
+** Append blob contents to another
60
+*/
61
+#define blob_appendb(dest, src) \
62
+ blob_append((dest), blob_buffer(src), blob_size(src))
63
+
64
+/*
65
+** Append a string literal to a blob
66
+** TODO: Consider renaming to blob_appendl()
67
+*/
68
+#define blob_append_literal(blob, literal) \
69
+ blob_append((blob), "" literal, (sizeof literal)-1)
70
+ /*
71
+ * The empty string in the second argument leads to a syntax error
72
+ * when the macro is not used with a string literal. Unfortunately
73
+ * the error is not overly explicit.
74
+ */
75
+
76
+/*
77
+** TODO: Suggested for removal because the name seems misleading.
78
+*/
79
+#define blob_append_string blob_append_literal
80
+
5881
/*
5982
** Seek whence parameter values
6083
*/
6184
#define BLOB_SEEK_SET 1
6285
#define BLOB_SEEK_CUR 2
@@ -325,17 +348,10 @@
325348
pBlob->nUsed += nData;
326349
pBlob->aData[pBlob->nUsed] = 0;
327350
memcpy(&pBlob->aData[nUsed], aData, nData);
328351
}
329352
330
-/*
331
-** Append a string literal to a blob.
332
-*/
333
-#if INTERFACE
334
-#define blob_append_string(BLOB,STR) blob_append(BLOB,STR,sizeof(STR)-1)
335
-#endif
336
-
337353
/*
338354
** Append a single character to the blob. If pBlob is zero then the
339355
** character is written directly to stdout.
340356
*/
341357
void blob_append_char(Blob *pBlob, char c){
@@ -503,11 +519,11 @@
503519
504520
/*
505521
** Compare two blobs. Return negative, zero, or positive if the first
506522
** blob is less then, equal to, or greater than the second.
507523
*/
508
-int blob_compare(Blob *pA, Blob *pB){
524
+int blob_compare(const Blob *pA, const Blob *pB){
509525
int szA, szB, sz, rc;
510526
blob_is_init(pA);
511527
blob_is_init(pB);
512528
szA = blob_size(pA);
513529
szB = blob_size(pB);
514530
--- src/blob.c
+++ src/blob.c
@@ -53,10 +53,33 @@
53 /*
54 ** The buffer holding the blob data
55 */
56 #define blob_buffer(X) ((X)->aData)
57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58 /*
59 ** Seek whence parameter values
60 */
61 #define BLOB_SEEK_SET 1
62 #define BLOB_SEEK_CUR 2
@@ -325,17 +348,10 @@
325 pBlob->nUsed += nData;
326 pBlob->aData[pBlob->nUsed] = 0;
327 memcpy(&pBlob->aData[nUsed], aData, nData);
328 }
329
330 /*
331 ** Append a string literal to a blob.
332 */
333 #if INTERFACE
334 #define blob_append_string(BLOB,STR) blob_append(BLOB,STR,sizeof(STR)-1)
335 #endif
336
337 /*
338 ** Append a single character to the blob. If pBlob is zero then the
339 ** character is written directly to stdout.
340 */
341 void blob_append_char(Blob *pBlob, char c){
@@ -503,11 +519,11 @@
503
504 /*
505 ** Compare two blobs. Return negative, zero, or positive if the first
506 ** blob is less then, equal to, or greater than the second.
507 */
508 int blob_compare(Blob *pA, Blob *pB){
509 int szA, szB, sz, rc;
510 blob_is_init(pA);
511 blob_is_init(pB);
512 szA = blob_size(pA);
513 szB = blob_size(pB);
514
--- src/blob.c
+++ src/blob.c
@@ -53,10 +53,33 @@
53 /*
54 ** The buffer holding the blob data
55 */
56 #define blob_buffer(X) ((X)->aData)
57
58 /*
59 ** Append blob contents to another
60 */
61 #define blob_appendb(dest, src) \
62 blob_append((dest), blob_buffer(src), blob_size(src))
63
64 /*
65 ** Append a string literal to a blob
66 ** TODO: Consider renaming to blob_appendl()
67 */
68 #define blob_append_literal(blob, literal) \
69 blob_append((blob), "" literal, (sizeof literal)-1)
70 /*
71 * The empty string in the second argument leads to a syntax error
72 * when the macro is not used with a string literal. Unfortunately
73 * the error is not overly explicit.
74 */
75
76 /*
77 ** TODO: Suggested for removal because the name seems misleading.
78 */
79 #define blob_append_string blob_append_literal
80
81 /*
82 ** Seek whence parameter values
83 */
84 #define BLOB_SEEK_SET 1
85 #define BLOB_SEEK_CUR 2
@@ -325,17 +348,10 @@
348 pBlob->nUsed += nData;
349 pBlob->aData[pBlob->nUsed] = 0;
350 memcpy(&pBlob->aData[nUsed], aData, nData);
351 }
352
 
 
 
 
 
 
 
353 /*
354 ** Append a single character to the blob. If pBlob is zero then the
355 ** character is written directly to stdout.
356 */
357 void blob_append_char(Blob *pBlob, char c){
@@ -503,11 +519,11 @@
519
520 /*
521 ** Compare two blobs. Return negative, zero, or positive if the first
522 ** blob is less then, equal to, or greater than the second.
523 */
524 int blob_compare(const Blob *pA, const Blob *pB){
525 int szA, szB, sz, rc;
526 blob_is_init(pA);
527 blob_is_init(pB);
528 szA = blob_size(pA);
529 szB = blob_size(pB);
530
--- src/default.css
+++ src/default.css
@@ -1665,10 +1665,86 @@
16651665
}
16661666
16671667
.monospace {
16681668
font-family: monospace;
16691669
}
1670
+
1671
+div.markdown > ol.footnotes {
1672
+ font-size: 90%;
1673
+}
1674
+div.markdown > ol.footnotes > li {
1675
+ margin-bottom: 0.5em;
1676
+}
1677
+div.markdown ol.footnotes > li.fn-joined > sup.fn-joined {
1678
+ color: gray;
1679
+ font-family: monospace;
1680
+}
1681
+div.markdown ol.footnotes > li.fn-joined > sup.fn-joined::after {
1682
+ content: "(joined from multiple locations) ";
1683
+}
1684
+div.markdown ol.footnotes > li.fn-misreference {
1685
+ margin-top: 0.75em;
1686
+ margin-bottom: 0.75em;
1687
+}
1688
+div.markdown ol.footnotes > li.fn-toodeep > i,
1689
+div.markdown ol.footnotes > li.fn-misreference,
1690
+div.markdown ol.footnotes > li.fn-unreferenced {
1691
+ color: gray;
1692
+}
1693
+div.markdown ol.footnotes > li.fn-misreference > span {
1694
+ color: red;
1695
+}
1696
+div.markdown ol.footnotes > li.fn-misreference > span::after {
1697
+ content: " (use of undefined label).";
1698
+}
1699
+div.markdown ol.footnotes > li.fn-unreferenced {
1700
+ padding-left: 0.5em;
1701
+}
1702
+div.markdown ol.footnotes > li.fn-unreferenced > code {
1703
+ color: red;
1704
+}
1705
+div.markdown ol.footnotes > li.fn-unreferenced > i::after {
1706
+ content: " was defined but is not referenced";
1707
+}
1708
+div.markdown ol.footnotes > li.fn-toodeep > i::after {
1709
+ content: " depth of nesting of inline footnotes exceeded the limit";
1710
+}
1711
+div.markdown ol.footnotes > li.fn-toodeep > pre,
1712
+div.markdown ol.footnotes > li.fn-unreferenced > pre {
1713
+ color: gray;
1714
+ font-size: 85%;
1715
+ padding-left: 0.5em;
1716
+ margin-top: 0.25em;
1717
+ border-left: 2px solid red;
1718
+}
1719
+div.markdown ol.footnotes > li.fn-toodeep > pre {
1720
+ margin-left: 0.5em;
1721
+}
1722
+div.markdown > ol.footnotes > li > .fn-backrefs {
1723
+ margin-right: 0.5em;
1724
+ font-weight: bold;
1725
+}
1726
+div.markdown > ol.footnotes > li > .fn-backrefs > a,
1727
+div.markdown sup.noteref > a {
1728
+ padding-left: 2px;
1729
+ padding-right: 2px;
1730
+}
1731
+div.markdown sup.noteref.misref,
1732
+div.markdown sup.noteref.misref > a {
1733
+ color: red;
1734
+ font-size: 90%;
1735
+}
1736
+div.markdown sup.noteref > a:target,
1737
+div.markdown span.notescope:target > sup.noteref > a,
1738
+div.markdown span.notescope:hover > sup.noteref > a,
1739
+div.markdown > ol.footnotes > li > .fn-backrefs > a:target {
1740
+ background: gold;
1741
+}
1742
+div.markdown span.notescope:hover,
1743
+div.markdown span.notescope:target {
1744
+ border-bottom: 2px solid gold;
1745
+}
16701746
16711747
/* Objects in the "desktoponly" class are invisible on mobile */
16721748
@media screen and (max-width: 600px) {
16731749
.desktoponly {
16741750
display: none;
16751751
--- src/default.css
+++ src/default.css
@@ -1665,10 +1665,86 @@
1665 }
1666
1667 .monospace {
1668 font-family: monospace;
1669 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1670
1671 /* Objects in the "desktoponly" class are invisible on mobile */
1672 @media screen and (max-width: 600px) {
1673 .desktoponly {
1674 display: none;
1675
--- src/default.css
+++ src/default.css
@@ -1665,10 +1665,86 @@
1665 }
1666
1667 .monospace {
1668 font-family: monospace;
1669 }
1670
1671 div.markdown > ol.footnotes {
1672 font-size: 90%;
1673 }
1674 div.markdown > ol.footnotes > li {
1675 margin-bottom: 0.5em;
1676 }
1677 div.markdown ol.footnotes > li.fn-joined > sup.fn-joined {
1678 color: gray;
1679 font-family: monospace;
1680 }
1681 div.markdown ol.footnotes > li.fn-joined > sup.fn-joined::after {
1682 content: "(joined from multiple locations) ";
1683 }
1684 div.markdown ol.footnotes > li.fn-misreference {
1685 margin-top: 0.75em;
1686 margin-bottom: 0.75em;
1687 }
1688 div.markdown ol.footnotes > li.fn-toodeep > i,
1689 div.markdown ol.footnotes > li.fn-misreference,
1690 div.markdown ol.footnotes > li.fn-unreferenced {
1691 color: gray;
1692 }
1693 div.markdown ol.footnotes > li.fn-misreference > span {
1694 color: red;
1695 }
1696 div.markdown ol.footnotes > li.fn-misreference > span::after {
1697 content: " (use of undefined label).";
1698 }
1699 div.markdown ol.footnotes > li.fn-unreferenced {
1700 padding-left: 0.5em;
1701 }
1702 div.markdown ol.footnotes > li.fn-unreferenced > code {
1703 color: red;
1704 }
1705 div.markdown ol.footnotes > li.fn-unreferenced > i::after {
1706 content: " was defined but is not referenced";
1707 }
1708 div.markdown ol.footnotes > li.fn-toodeep > i::after {
1709 content: " depth of nesting of inline footnotes exceeded the limit";
1710 }
1711 div.markdown ol.footnotes > li.fn-toodeep > pre,
1712 div.markdown ol.footnotes > li.fn-unreferenced > pre {
1713 color: gray;
1714 font-size: 85%;
1715 padding-left: 0.5em;
1716 margin-top: 0.25em;
1717 border-left: 2px solid red;
1718 }
1719 div.markdown ol.footnotes > li.fn-toodeep > pre {
1720 margin-left: 0.5em;
1721 }
1722 div.markdown > ol.footnotes > li > .fn-backrefs {
1723 margin-right: 0.5em;
1724 font-weight: bold;
1725 }
1726 div.markdown > ol.footnotes > li > .fn-backrefs > a,
1727 div.markdown sup.noteref > a {
1728 padding-left: 2px;
1729 padding-right: 2px;
1730 }
1731 div.markdown sup.noteref.misref,
1732 div.markdown sup.noteref.misref > a {
1733 color: red;
1734 font-size: 90%;
1735 }
1736 div.markdown sup.noteref > a:target,
1737 div.markdown span.notescope:target > sup.noteref > a,
1738 div.markdown span.notescope:hover > sup.noteref > a,
1739 div.markdown > ol.footnotes > li > .fn-backrefs > a:target {
1740 background: gold;
1741 }
1742 div.markdown span.notescope:hover,
1743 div.markdown span.notescope:target {
1744 border-bottom: 2px solid gold;
1745 }
1746
1747 /* Objects in the "desktoponly" class are invisible on mobile */
1748 @media screen and (max-width: 600px) {
1749 .desktoponly {
1750 display: none;
1751
+30
--- src/encode.c
+++ src/encode.c
@@ -205,10 +205,40 @@
205205
** by this routine.
206206
*/
207207
char *urlize(const char *z, int n){
208208
return EncodeHttp(z, n, 0);
209209
}
210
+
211
+/*
212
+** If input string does not contain quotes (niether ' nor ")
213
+** then return the argument itself. Otherwise return a newly allocated
214
+** copy of input with all quotes %-escaped.
215
+*/
216
+const char* escape_quotes(const char *zIn){
217
+ char *zRet, *zOut;
218
+ size_t i, n = 0;
219
+ for(i=0; zIn[i]; i++){
220
+ if( zIn[i]== '"' || zIn[i]== '\'' ) n++;
221
+ }
222
+ if( !n ) return zIn;
223
+ zRet = zOut = fossil_malloc( i + 2*n + 1 );
224
+ for(i=0; zIn[i]; i++){
225
+ if( zIn[i]=='"' ){
226
+ *(zOut++) = '%';
227
+ *(zOut++) = '2';
228
+ *(zOut++) = '2';
229
+ }else if( zIn[i]=='\'' ){
230
+ *(zOut++) = '%';
231
+ *(zOut++) = '2';
232
+ *(zOut++) = '7';
233
+ }else{
234
+ *(zOut++) = zIn[i];
235
+ }
236
+ }
237
+ *zOut = 0;
238
+ return zRet;
239
+}
210240
211241
/*
212242
** Convert a single HEX digit to an integer
213243
*/
214244
static int AsciiToHex(int c){
215245
--- src/encode.c
+++ src/encode.c
@@ -205,10 +205,40 @@
205 ** by this routine.
206 */
207 char *urlize(const char *z, int n){
208 return EncodeHttp(z, n, 0);
209 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
211 /*
212 ** Convert a single HEX digit to an integer
213 */
214 static int AsciiToHex(int c){
215
--- src/encode.c
+++ src/encode.c
@@ -205,10 +205,40 @@
205 ** by this routine.
206 */
207 char *urlize(const char *z, int n){
208 return EncodeHttp(z, n, 0);
209 }
210
211 /*
212 ** If input string does not contain quotes (niether ' nor ")
213 ** then return the argument itself. Otherwise return a newly allocated
214 ** copy of input with all quotes %-escaped.
215 */
216 const char* escape_quotes(const char *zIn){
217 char *zRet, *zOut;
218 size_t i, n = 0;
219 for(i=0; zIn[i]; i++){
220 if( zIn[i]== '"' || zIn[i]== '\'' ) n++;
221 }
222 if( !n ) return zIn;
223 zRet = zOut = fossil_malloc( i + 2*n + 1 );
224 for(i=0; zIn[i]; i++){
225 if( zIn[i]=='"' ){
226 *(zOut++) = '%';
227 *(zOut++) = '2';
228 *(zOut++) = '2';
229 }else if( zIn[i]=='\'' ){
230 *(zOut++) = '%';
231 *(zOut++) = '2';
232 *(zOut++) = '7';
233 }else{
234 *(zOut++) = zIn[i];
235 }
236 }
237 *zOut = 0;
238 return zRet;
239 }
240
241 /*
242 ** Convert a single HEX digit to an integer
243 */
244 static int AsciiToHex(int c){
245
+1
--- src/main.c
+++ src/main.c
@@ -324,10 +324,11 @@
324324
} reqPayload; /* request payload object (if any) */
325325
cson_array *warnings; /* response warnings */
326326
int timerId; /* fetched from fossil_timer_start() */
327327
} json;
328328
#endif /* FOSSIL_ENABLE_JSON */
329
+ int ftntsIssues[4]; /* Counts for misref, strayed, joined, overnested */
329330
int diffCnt[3]; /* Counts for DIFF_NUMSTAT: files, ins, del */
330331
};
331332
332333
/*
333334
** Macro for debugging:
334335
--- src/main.c
+++ src/main.c
@@ -324,10 +324,11 @@
324 } reqPayload; /* request payload object (if any) */
325 cson_array *warnings; /* response warnings */
326 int timerId; /* fetched from fossil_timer_start() */
327 } json;
328 #endif /* FOSSIL_ENABLE_JSON */
 
329 int diffCnt[3]; /* Counts for DIFF_NUMSTAT: files, ins, del */
330 };
331
332 /*
333 ** Macro for debugging:
334
--- src/main.c
+++ src/main.c
@@ -324,10 +324,11 @@
324 } reqPayload; /* request payload object (if any) */
325 cson_array *warnings; /* response warnings */
326 int timerId; /* fetched from fossil_timer_start() */
327 } json;
328 #endif /* FOSSIL_ENABLE_JSON */
329 int ftntsIssues[4]; /* Counts for misref, strayed, joined, overnested */
330 int diffCnt[3]; /* Counts for DIFF_NUMSTAT: files, ins, del */
331 };
332
333 /*
334 ** Macro for debugging:
335
+730 -131
--- src/markdown.c
+++ src/markdown.c
@@ -45,10 +45,11 @@
4545
/* mkd_renderer -- functions for rendering parsed data */
4646
struct mkd_renderer {
4747
/* document level callbacks */
4848
void (*prolog)(struct Blob *ob, void *opaque);
4949
void (*epilog)(struct Blob *ob, void *opaque);
50
+ void (*footnotes)(struct Blob *ob, const struct Blob *items, void *opaque);
5051
5152
/* block level callbacks - NULL skips the block */
5253
void (*blockcode)(struct Blob *ob, struct Blob *text, void *opaque);
5354
void (*blockquote)(struct Blob *ob, struct Blob *text, void *opaque);
5455
void (*blockhtml)(struct Blob *ob, struct Blob *text, void *opaque);
@@ -63,10 +64,12 @@
6364
void *opaque);
6465
void (*table_cell)(struct Blob *ob, struct Blob *text, int flags,
6566
void *opaque);
6667
void (*table_row)(struct Blob *ob, struct Blob *cells, int flags,
6768
void *opaque);
69
+ void (*footnote_item)(struct Blob *ob, const struct Blob *text,
70
+ int index, int nUsed, void *opaque);
6871
6972
/* span level callbacks - NULL or return 0 prints the span verbatim */
7073
int (*autolink)(struct Blob *ob, struct Blob *link,
7174
enum mkd_autolink type, void *opaque);
7275
int (*codespan)(struct Blob *ob, struct Blob *text, int nSep, void *opaque);
@@ -79,10 +82,12 @@
7982
int (*link)(struct Blob *ob, struct Blob *link, struct Blob *title,
8083
struct Blob *content, void *opaque);
8184
int (*raw_html_tag)(struct Blob *ob, struct Blob *tag, void *opaque);
8285
int (*triple_emphasis)(struct Blob *ob, struct Blob *text,
8386
char c, void *opaque);
87
+ int (*footnote_ref)(struct Blob *ob, const struct Blob *span,
88
+ const struct Blob *upc, int index, int locus, void *opaque);
8489
8590
/* low level callbacks - NULL copies input directly into the output */
8691
void (*entity)(struct Blob *ob, struct Blob *entity, void *opaque);
8792
void (*normal_text)(struct Blob *ob, struct Blob *text, void *opaque);
8893
@@ -113,31 +118,54 @@
113118
114119
/**********************
115120
* EXPORTED FUNCTIONS *
116121
**********************/
117122
118
-/* markdown -- parses the input buffer and renders it into the output buffer */
123
+/*
124
+** markdown -- parses the input buffer and renders it into the output buffer.
125
+*/
119126
void markdown(
120127
struct Blob *ob,
121
- struct Blob *ib,
128
+ const struct Blob *ib,
122129
const struct mkd_renderer *rndr);
123130
124131
125132
#endif /* INTERFACE */
126133
134
+#define BLOB_COUNT(pBlob,el_type) (blob_size(pBlob)/sizeof(el_type))
135
+#define COUNT_FOOTNOTES(pBlob) BLOB_COUNT(pBlob,struct footnote)
136
+#define CAST_AS_FOOTNOTES(pBlob) ((struct footnote*)blob_buffer(pBlob))
127137
128138
/***************
129139
* LOCAL TYPES *
130140
***************/
131141
132
-/* link_ref -- reference to a link */
142
+/*
143
+** link_ref -- reference to a link.
144
+*/
133145
struct link_ref {
134
- struct Blob id;
146
+ struct Blob id; /* must be the first field as in footnote struct */
135147
struct Blob link;
136148
struct Blob title;
137149
};
138150
151
+/*
152
+** A footnote's data.
153
+** id, text, and upc fields must be in that particular order.
154
+*/
155
+struct footnote {
156
+ struct Blob id; /* must be the first field as in link_ref struct */
157
+ struct Blob text; /* footnote's content that is rendered at the end */
158
+ struct Blob upc; /* user-provided classes .ASCII-alnum.or-hypen: */
159
+ int bRndred; /* indicates if `text` holds a rendered content */
160
+
161
+ int defno; /* serial number of definition, set during the first pass */
162
+ int index; /* set to the index within array after ordering by id */
163
+ int iMark; /* user-visible numeric marker, assigned upon the first use*/
164
+ int nUsed; /* counts references to this note, increments upon each use*/
165
+};
166
+#define FOOTNOTE_INITIALIZER {empty_blob,empty_blob,empty_blob, 0,0,0,0,0}
139167
140168
/* char_trigger -- function pointer to render active chars */
141169
/* returns the number of chars taken care of */
142170
/* data is the pointer of the beginning of the span */
143171
/* offset is the number of valid chars before data */
@@ -156,12 +184,19 @@
156184
struct Blob refs;
157185
char_trigger active_char[256];
158186
int iDepth; /* Depth of recursion */
159187
int nBlobCache; /* Number of entries in aBlobCache */
160188
struct Blob *aBlobCache[20]; /* Cache of Blobs available for reuse */
189
+
190
+ struct {
191
+ Blob all; /* Buffer that holds array of footnotes. Its underline
192
+ memory may be reallocated when a new footnote is added. */
193
+ int nLbled; /* number of labeled footnotes found during the first pass */
194
+ int nMarks; /* counts distinct indices found during the second pass */
195
+ struct footnote misref; /* nUsed counts misreferences, iMark must be -1 */
196
+ } notes;
161197
};
162
-
163198
164199
/* html_tag -- structure for quick HTML tag search (inspired from discount) */
165200
struct html_tag {
166201
const char *text;
167202
int size;
@@ -183,16 +218,18 @@
183218
{ "html", 4 },
184219
{ "pre", 3 },
185220
{ "script", 6 },
186221
};
187222
188
-
189223
/***************************
190224
* STATIC HELPER FUNCTIONS *
191225
***************************/
192226
193
-/* build_ref_id -- collapse whitespace from input text to make it a ref id */
227
+/*
228
+** build_ref_id -- collapse whitespace from input text to make it a ref id.
229
+** Potential TODO: maybe also handle CR+LF line endings?
230
+*/
194231
static int build_ref_id(struct Blob *id, const char *data, size_t size){
195232
size_t beg, i;
196233
char *id_data;
197234
198235
/* skip leading whitespace */
@@ -247,10 +284,58 @@
247284
struct link_ref *lra = (void *)a;
248285
struct link_ref *lrb = (void *)b;
249286
return blob_compare(&lra->id, &lrb->id);
250287
}
251288
289
+/*
290
+** cmp_footnote_id -- comparison function for footnotes qsort.
291
+** Empty IDs sort last (in undetermined order).
292
+** Equal IDs are sorted in the order of definition in the source.
293
+*/
294
+static int cmp_footnote_id(const void *fna, const void *fnb){
295
+ const struct footnote *a = fna, *b = fnb;
296
+ const int szA = blob_size(&a->id), szB = blob_size(&b->id);
297
+ if( szA ){
298
+ if( szB ){
299
+ int cmp = blob_compare(&a->id, &b->id);
300
+ if( cmp ) return cmp;
301
+ }else return -1;
302
+ }else return szB ? 1 : 0;
303
+ /* IDs are equal and non-empty */
304
+ if( a->defno < b->defno ) return -1;
305
+ if( a->defno > b->defno ) return 1;
306
+ assert(!"reachable");
307
+ return 0; /* should never reach here */
308
+}
309
+
310
+/*
311
+** cmp_footnote_sort -- comparison function for footnotes qsort.
312
+** Unreferenced footnotes (when nUsed == 0) sort last and
313
+** are sorted in the order of definition in the source.
314
+*/
315
+static int cmp_footnote_sort(const void *fna, const void *fnb){
316
+ const struct footnote *a = fna, *b = fnb;
317
+ int i, j;
318
+ assert( a->nUsed >= 0 );
319
+ assert( b->nUsed >= 0 );
320
+ assert( a->defno >= 0 );
321
+ assert( b->defno >= 0 );
322
+ if( a->nUsed ){
323
+ assert( a->iMark > 0 );
324
+ if( !b->nUsed ) return -1;
325
+ assert( b->iMark > 0 );
326
+ i = a->iMark;
327
+ j = b->iMark;
328
+ }else{
329
+ if( b->nUsed ) return 1;
330
+ i = a->defno;
331
+ j = b->defno;
332
+ }
333
+ if( i < j ) return -1;
334
+ if( i > j ) return 1;
335
+ return 0;
336
+}
252337
253338
/* cmp_html_tag -- comparison function for bsearch() (stolen from discount) */
254339
static int cmp_html_tag(const void *a, const void *b){
255340
const struct html_tag *hta = a;
256341
const struct html_tag *htb = b;
@@ -579,12 +664,14 @@
579664
if( fossil_isalnum(after) ) return 0;
580665
return 1;
581666
}
582667
583668
584
-/* parse_emph1 -- parsing single emphasis */
585
-/* closed by a symbol not preceded by whitespace and not followed by symbol */
669
+/*
670
+** parse_emph1 -- parsing single emphasis.
671
+** closed by a symbol not preceded by whitespace and not followed by symbol.
672
+*/
586673
static size_t parse_emph1(
587674
struct Blob *ob,
588675
struct render *rndr,
589676
char *data,
590677
size_t size,
@@ -624,12 +711,13 @@
624711
}
625712
}
626713
return 0;
627714
}
628715
629
-
630
-/* parse_emph2 -- parsing single emphasis */
716
+/*
717
+** parse_emph2 -- parsing single emphasis.
718
+*/
631719
static size_t parse_emph2(
632720
struct Blob *ob,
633721
struct render *rndr,
634722
char *data,
635723
size_t size,
@@ -663,13 +751,14 @@
663751
i++;
664752
}
665753
return 0;
666754
}
667755
668
-
669
-/* parse_emph3 -- parsing single emphasis */
670
-/* finds the first closing tag, and delegates to the other emph */
756
+/*
757
+** parse_emph3 -- parsing single emphasis.
758
+** finds the first closing tag, and delegates to the other emph.
759
+*/
671760
static size_t parse_emph3(
672761
struct Blob *ob,
673762
struct render *rndr,
674763
char *data,
675764
size_t size,
@@ -711,12 +800,13 @@
711800
}
712801
}
713802
return 0;
714803
}
715804
716
-
717
-/* char_emphasis -- single and double emphasis parsing */
805
+/*
806
+** char_emphasis -- single and double emphasis parsing.
807
+*/
718808
static size_t char_emphasis(
719809
struct Blob *ob,
720810
struct render *rndr,
721811
char *data,
722812
size_t offset,
@@ -756,12 +846,13 @@
756846
return ret+3;
757847
}
758848
return 0;
759849
}
760850
761
-
762
-/* char_linebreak -- '\n' preceded by two spaces (assuming linebreak != 0) */
851
+/*
852
+** char_linebreak -- '\n' preceded by two spaces (assuming linebreak != 0).
853
+*/
763854
static size_t char_linebreak(
764855
struct Blob *ob,
765856
struct render *rndr,
766857
char *data,
767858
size_t offset,
@@ -771,12 +862,13 @@
771862
/* removing the last space from ob and rendering */
772863
if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]==' ' ) ob->nUsed--;
773864
return rndr->make.linebreak(ob, rndr->make.opaque) ? 1 : 0;
774865
}
775866
776
-
777
-/* char_codespan -- '`' parsing a code span (assuming codespan != 0) */
867
+/*
868
+** char_codespan -- '`' parsing a code span (assuming codespan != 0).
869
+*/
778870
static size_t char_codespan(
779871
struct Blob *ob,
780872
struct render *rndr,
781873
char *data,
782874
size_t offset,
@@ -813,11 +905,13 @@
813905
}
814906
return end;
815907
}
816908
817909
818
-/* char_escape -- '\\' backslash escape */
910
+/*
911
+** char_escape -- '\\' backslash escape.
912
+*/
819913
static size_t char_escape(
820914
struct Blob *ob,
821915
struct render *rndr,
822916
char *data,
823917
size_t offset,
@@ -833,13 +927,14 @@
833927
}
834928
}
835929
return 2;
836930
}
837931
838
-
839
-/* char_entity -- '&' escaped when it doesn't belong to an entity */
840
-/* valid entities are assumed to be anything matching &#?[A-Za-z0-9]+; */
932
+/*
933
+** char_entity -- '&' escaped when it doesn't belong to an entity.
934
+** valid entities are assumed to be anything matching &#?[A-Za-z0-9]+;
935
+*/
841936
static size_t char_entity(
842937
struct Blob *ob,
843938
struct render *rndr,
844939
char *data,
845940
size_t offset,
@@ -869,12 +964,13 @@
869964
blob_append(ob, data, end);
870965
}
871966
return end;
872967
}
873968
874
-
875
-/* char_langle_tag -- '<' when tags or autolinks are allowed */
969
+/*
970
+** char_langle_tag -- '<' when tags or autolinks are allowed.
971
+*/
876972
static size_t char_langle_tag(
877973
struct Blob *ob,
878974
struct render *rndr,
879975
char *data,
880976
size_t offset,
@@ -899,12 +995,12 @@
899995
}else{
900996
return end;
901997
}
902998
}
903999
904
-
905
-/* get_link_inline -- extract inline-style link and title from
1000
+/*
1001
+** get_link_inline -- extract inline-style link and title from
9061002
** parenthesed data
9071003
*/
9081004
static int get_link_inline(
9091005
struct Blob *link,
9101006
struct Blob *title,
@@ -954,11 +1050,11 @@
9541050
link_e--;
9551051
}
9561052
9571053
/* remove optional angle brackets around the link */
9581054
if( data[link_b]=='<' ) link_b += 1;
959
- if( data[link_e-1]=='>' ) link_e -= 1;
1055
+ if( link_e && data[link_e-1]=='>' ) link_e -= 1;
9601056
9611057
/* escape backslashed character from link */
9621058
blob_reset(link);
9631059
i = link_b;
9641060
while( i<link_e ){
@@ -975,145 +1071,353 @@
9751071
/* this function always succeed */
9761072
return 0;
9771073
}
9781074
9791075
980
-/* get_link_ref -- extract referenced link and title from id */
1076
+/*
1077
+** get_link_ref -- extract referenced link and title from id.
1078
+*/
9811079
static int get_link_ref(
9821080
struct render *rndr,
9831081
struct Blob *link,
9841082
struct Blob *title,
9851083
char *data,
9861084
size_t size
9871085
){
9881086
struct link_ref *lr;
1087
+ const size_t sz = blob_size(&rndr->refs);
9891088
9901089
/* find the link from its id (stored temporarily in link) */
9911090
blob_reset(link);
992
- if( build_ref_id(link, data, size)<0 ) return -1;
1091
+ if( !sz || build_ref_id(link, data, size)<0 ) return -1;
9931092
lr = bsearch(link,
9941093
blob_buffer(&rndr->refs),
995
- blob_size(&rndr->refs)/sizeof(struct link_ref),
1094
+ sz/sizeof(struct link_ref),
9961095
sizeof (struct link_ref),
9971096
cmp_link_ref);
9981097
if( !lr ) return -1;
9991098
10001099
/* fill the output buffers */
10011100
blob_reset(link);
10021101
blob_reset(title);
1003
- blob_append(link, blob_buffer(&lr->link), blob_size(&lr->link));
1004
- blob_append(title, blob_buffer(&lr->title), blob_size(&lr->title));
1102
+ blob_appendb(link, &lr->link);
1103
+ blob_appendb(title, &lr->title);
1104
+ return 0;
1105
+}
1106
+
1107
+/*
1108
+** get_footnote() -- find a footnote by label, invoked during the 2nd pass.
1109
+** If found then return a shallow copy of the corresponding footnote;
1110
+** otherwise return a shallow copy of rndr->notes.misref.
1111
+** In both cases corresponding `nUsed` field is incremented before return.
1112
+*/
1113
+static struct footnote get_footnote(
1114
+ struct render *rndr,
1115
+ const char *data,
1116
+ size_t size
1117
+){
1118
+ struct footnote *fn = 0;
1119
+ struct Blob *id;
1120
+ if( !rndr->notes.nLbled ) goto fallback;
1121
+ id = new_work_buffer(rndr);
1122
+ if( build_ref_id(id, data, size)<0 ) goto cleanup;
1123
+ fn = bsearch(id, blob_buffer(&rndr->notes.all),
1124
+ rndr->notes.nLbled,
1125
+ sizeof (struct footnote),
1126
+ cmp_link_ref);
1127
+ if( !fn ) goto cleanup;
1128
+
1129
+ if( fn->nUsed == 0 ){ /* the first reference to the footnote */
1130
+ assert( fn->iMark == 0 );
1131
+ fn->iMark = ++(rndr->notes.nMarks);
1132
+ }
1133
+ assert( fn->iMark > 0 );
1134
+cleanup:
1135
+ release_work_buffer( rndr, id );
1136
+fallback:
1137
+ if( !fn ) fn = &rndr->notes.misref;
1138
+ fn->nUsed++;
1139
+ assert( fn->nUsed > 0 );
1140
+ return *fn;
1141
+}
1142
+
1143
+/*
1144
+** Counts characters in the blank prefix within at most nHalfLines.
1145
+** A sequence of spaces and tabs counts as odd halfline,
1146
+** a newline counts as even halfline.
1147
+** If nHalfLines < 0 then proceed without constraints.
1148
+*/
1149
+static inline size_t sizeof_blank_prefix(
1150
+ const char *data, size_t size, int nHalfLines
1151
+){
1152
+ const char *p = data;
1153
+ const char * const end = data+size;
1154
+ if( nHalfLines < 0 ){
1155
+ while( p!=end && fossil_isspace(*p) ){
1156
+ p++;
1157
+ }
1158
+ }else while( nHalfLines > 0 ){
1159
+ while( p!=end && (*p==' ' || *p=='\t' ) ){ p++; }
1160
+ if( p==end || --nHalfLines == 0 ) break;
1161
+ if( *p=='\n' || *p=='\r' ){
1162
+ p++;
1163
+ if( p==end ) break;
1164
+ if( *p=='\n' && p[-1]=='\r' ){
1165
+ p++;
1166
+ }
1167
+ }
1168
+ nHalfLines--;
1169
+ }
1170
+ return p-data;
1171
+}
1172
+
1173
+/*
1174
+** Check if the data starts with a classlist token of the special form.
1175
+** If so then return the length of that token, otherwise return 0.
1176
+**
1177
+** The token must start with a dot and must end with a colon;
1178
+** in between of these it must be a dot-separated list of words;
1179
+** each word may contain only alphanumeric characters and hyphens.
1180
+**
1181
+** If `bBlank` is non-zero then a blank character must follow
1182
+** the token's ending colon: otherwise function returns 0
1183
+** despite the well-formed token.
1184
+*/
1185
+static size_t is_footnote_classlist(const char * const data, size_t size,
1186
+ int bBlank){
1187
+ const char *p;
1188
+ const char * const end = data+size;
1189
+ if( data==end || *data != '.' ) return 0;
1190
+ for(p=data+1; p!=end; p++){
1191
+ if( fossil_isalnum(*p) || *p=='-' ) continue;
1192
+ if( p[-1]=='.' ) break;
1193
+ if( *p==':' ){
1194
+ p++;
1195
+ if( bBlank ){
1196
+ if( p==end || !fossil_isspace(*p) ) break;
1197
+ }
1198
+ return p-data;
1199
+ }
1200
+ if( *p!='.' ) break;
1201
+ }
1202
+ return 0;
1203
+}
1204
+
1205
+/*
1206
+** Adds unlabeled footnote to the rndr->notes.all.
1207
+** On success puts a shallow copy of the constructed footnote into pFN
1208
+** and returns 1, otherwise pFN is unchanged and 0 is returned.
1209
+*/
1210
+static inline int add_inline_footnote(
1211
+ struct render *rndr,
1212
+ const char *text,
1213
+ size_t size,
1214
+ struct footnote* pFN
1215
+){
1216
+ struct footnote fn = FOOTNOTE_INITIALIZER, *last;
1217
+ const char *zUPC = 0;
1218
+ size_t nUPC = 0, n = sizeof_blank_prefix(text, size, 3);
1219
+ if( n >= size ) return 0;
1220
+ text += n;
1221
+ size -= n;
1222
+ nUPC = is_footnote_classlist(text, size, 1);
1223
+ if( nUPC ){
1224
+ assert( nUPC<size );
1225
+ zUPC = text;
1226
+ text += nUPC;
1227
+ size -= nUPC;
1228
+ }
1229
+ if( sizeof_blank_prefix(text,size,-1)==size ){
1230
+ if( !nUPC ) return 0; /* empty inline footnote */
1231
+ text = zUPC;
1232
+ size = nUPC; /* bare classlist is treated */
1233
+ nUPC = 0; /* as plain text */
1234
+ }
1235
+ fn.iMark = ++(rndr->notes.nMarks);
1236
+ fn.nUsed = 1;
1237
+ fn.index = COUNT_FOOTNOTES(&rndr->notes.all);
1238
+ assert( fn.iMark > 0 );
1239
+ blob_append(&fn.text, text, size);
1240
+ if(nUPC) blob_append(&fn.upc, zUPC, nUPC);
1241
+ blob_append(&rndr->notes.all, (char *)&fn, sizeof fn);
1242
+ last = (struct footnote*)( blob_buffer(&rndr->notes.all)
1243
+ +( blob_size(&rndr->notes.all)-sizeof fn ));
1244
+ assert( pFN );
1245
+ memcpy( pFN, last, sizeof fn );
1246
+ return 1;
1247
+}
1248
+
1249
+/*
1250
+** Return the byte offset of the matching closing bracket or 0 if not
1251
+** found. begin[0] must be either '[' or '('.
1252
+**
1253
+** TODO: It seems that things like "\\(" are not handled correctly.
1254
+** That is historical behavior for a corner-case,
1255
+** so it's left as it is until somebody complains.
1256
+*/
1257
+static inline size_t matching_bracket_offset(
1258
+ const char* begin,
1259
+ const char* end
1260
+){
1261
+ const char *i;
1262
+ int level;
1263
+ const char bra = *begin;
1264
+ const char ket = bra=='[' ? ']' : ')';
1265
+ assert( bra=='[' || bra=='(' );
1266
+ for(i=begin+1,level=1; i!=end; i++){
1267
+ if( *i=='\n' ) /* do nothing */;
1268
+ else if( i[-1]=='\\' ) continue;
1269
+ else if( *i==bra ) level++;
1270
+ else if( *i==ket ){
1271
+ if( --level<=0 ) return i-begin;
1272
+ }
1273
+ }
10051274
return 0;
10061275
}
10071276
1277
+/*
1278
+** char_footnote -- '(': parsing a standalone inline footnote.
1279
+*/
1280
+static size_t char_footnote(
1281
+ struct Blob *ob,
1282
+ struct render *rndr,
1283
+ char *data,
1284
+ size_t offset,
1285
+ size_t size
1286
+){
1287
+ size_t end;
1288
+ struct footnote fn;
10081289
1009
-/* char_link -- '[': parsing a link or an image */
1290
+ if( size<4 || data[1]!='^' ) return 0;
1291
+ end = matching_bracket_offset(data, data+size);
1292
+ if( !end ) return 0;
1293
+ if( !add_inline_footnote(rndr, data+2, end-2, &fn) ) return 0;
1294
+ if( rndr->make.footnote_ref ){
1295
+ rndr->make.footnote_ref(ob,0,&fn.upc,fn.iMark,1,rndr->make.opaque);
1296
+ }
1297
+ return end+1;
1298
+}
1299
+
1300
+/*
1301
+** char_link -- '[': parsing a link or an image.
1302
+*/
10101303
static size_t char_link(
10111304
struct Blob *ob,
10121305
struct render *rndr,
10131306
char *data,
10141307
size_t offset,
1015
- size_t size
1308
+ size_t size /* parse_inline() ensures that size > 0 */
10161309
){
1017
- int is_img = (offset && data[-1] == '!'), level;
1310
+ const int is_img = (offset && data[-1] == '!');
10181311
size_t i = 1, txt_e;
10191312
struct Blob *content = 0;
10201313
struct Blob *link = 0;
10211314
struct Blob *title = 0;
1315
+ struct footnote fn;
10221316
int ret;
10231317
10241318
/* checking whether the correct renderer exists */
10251319
if( (is_img && !rndr->make.image) || (!is_img && !rndr->make.link) ){
10261320
return 0;
10271321
}
10281322
10291323
/* looking for the matching closing bracket */
1030
- for(level=1; i<size; i++){
1031
- if( data[i]=='\n' ) /* do nothing */;
1032
- else if( data[i-1]=='\\' ) continue;
1033
- else if( data[i]=='[' ) level += 1;
1034
- else if( data[i]==']' ){
1035
- level--;
1036
- if( level<=0 ) break;
1037
- }
1038
- }
1039
- if( i>=size ) return 0;
1040
- txt_e = i;
1041
- i++;
1042
-
1043
- /* skip any amount of whitespace or newline */
1044
- /* (this is much more laxist than original markdown syntax) */
1045
- while( i<size && (data[i]==' ' || data[i]=='\t' || data[i]=='\n') ){ i++; }
1046
-
1047
- /* allocate temporary buffers to store content, link and title */
1048
- title = new_work_buffer(rndr);
1049
- content = new_work_buffer(rndr);
1050
- link = new_work_buffer(rndr);
1324
+ txt_e = matching_bracket_offset(data, data+size);
1325
+ if( !txt_e ) return 0;
1326
+ i = txt_e + 1;
10511327
ret = 0; /* error if we don't get to the callback */
1052
-
1053
- /* inline style link */
1054
- if( i<size && data[i]=='(' ){
1055
- size_t span_end = i;
1056
- while( span_end<size
1057
- && !(data[span_end]==')' && (span_end==i || data[span_end-1]!='\\'))
1058
- ){
1059
- span_end++;
1060
- }
1061
-
1062
- if( span_end>=size
1063
- || get_link_inline(link, title, data+i+1, span_end-(i+1))<0
1064
- ){
1065
- goto char_link_cleanup;
1066
- }
1067
-
1068
- i = span_end+1;
1069
-
1070
- /* reference style link */
1071
- }else if( i<size && data[i]=='[' ){
1072
- char *id_data;
1073
- size_t id_size, id_end = i;
1074
-
1075
- while( id_end<size && data[id_end]!=']' ){ id_end++; }
1076
-
1077
- if( id_end>=size ) goto char_link_cleanup;
1078
-
1079
- if( i+1==id_end ){
1080
- /* implicit id - use the contents */
1081
- id_data = data+1;
1082
- id_size = txt_e-1;
1083
- }else{
1084
- /* explicit id - between brackets */
1085
- id_data = data+i+1;
1086
- id_size = id_end-(i+1);
1087
- }
1088
-
1089
- if( get_link_ref(rndr, link, title, id_data, id_size)<0 ){
1090
- goto char_link_cleanup;
1091
- }
1092
-
1093
- i = id_end+1;
1094
-
1095
- /* shortcut reference style link */
1096
- }else{
1097
- if( get_link_ref(rndr, link, title, data+1, txt_e-1)<0 ){
1098
- goto char_link_cleanup;
1099
- }
1100
-
1101
- /* rewinding the whitespace */
1102
- i = txt_e+1;
1103
- }
1104
-
1328
+ fn.nUsed = 0;
1329
+
1330
+ /* free-standing footnote refernece */
1331
+ if(!is_img && size>3 && data[1]=='^'){
1332
+ fn = get_footnote(rndr, data+2, txt_e-2);
1333
+ }else{
1334
+
1335
+ /* skip "inter-bracket-whitespace" - any amount of whitespace or newline */
1336
+ /* (this is much more lax than original markdown syntax) */
1337
+ while( i<size && (data[i]==' ' || data[i]=='\t' || data[i]=='\n') ){ i++; }
1338
+
1339
+ /* allocate temporary buffers to store content, link and title */
1340
+ title = new_work_buffer(rndr);
1341
+ content = new_work_buffer(rndr);
1342
+ link = new_work_buffer(rndr);
1343
+
1344
+ if( i<size && data[i]=='(' ){
1345
+
1346
+ if( i+2<size && data[i+1]=='^' ){ /* span-bounded inline footnote */
1347
+
1348
+ const size_t k = matching_bracket_offset(data+i, data+size);
1349
+ if( !k ) goto char_link_cleanup;
1350
+ add_inline_footnote(rndr, data+(i+2), k-2, &fn);
1351
+ i += k+1;
1352
+ }else{ /* inline style link */
1353
+ size_t span_end = i;
1354
+ while( span_end<size
1355
+ && !(data[span_end]==')'
1356
+ && (span_end==i || data[span_end-1]!='\\')) ){
1357
+ span_end++;
1358
+ }
1359
+ if( span_end>=size
1360
+ || get_link_inline(link, title, data+i+1, span_end-(i+1))<0 ){
1361
+ goto char_link_cleanup;
1362
+ }
1363
+ i = span_end+1;
1364
+ }
1365
+ /* reference style link or span-bounded footnote reference */
1366
+ }else if( i<size && data[i]=='[' ){
1367
+ char *id_data;
1368
+ size_t id_size, id_end = i;
1369
+ int bFootnote;
1370
+
1371
+ while( id_end<size && data[id_end]!=']' ){ id_end++; }
1372
+ if( id_end>=size ) goto char_link_cleanup;
1373
+ bFootnote = data[i+1]=='^';
1374
+ if( i+1==id_end || (bFootnote && i+2==id_end) ){
1375
+ /* implicit id - use the contents */
1376
+ id_data = data+1;
1377
+ id_size = txt_e-1;
1378
+ }else{
1379
+ /* explicit id - between brackets */
1380
+ id_data = data+i+1;
1381
+ id_size = id_end-(i+1);
1382
+ if( bFootnote ){
1383
+ id_data++;
1384
+ id_size--;
1385
+ }
1386
+ }
1387
+ if( bFootnote ){
1388
+ fn = get_footnote(rndr, id_data, id_size);
1389
+ }else if( get_link_ref(rndr, link, title, id_data, id_size)<0 ){
1390
+ goto char_link_cleanup;
1391
+ }
1392
+ i = id_end+1;
1393
+ /* shortcut reference style link */
1394
+ }else{
1395
+ if( get_link_ref(rndr, link, title, data+1, txt_e-1)<0 ){
1396
+ goto char_link_cleanup;
1397
+ }
1398
+ /* rewinding an "inter-bracket-whitespace" */
1399
+ i = txt_e+1;
1400
+ }
1401
+ }
11051402
/* building content: img alt is escaped, link content is parsed */
1106
- if( txt_e>1 ){
1403
+ if( txt_e>1 && content ){
11071404
if( is_img ) blob_append(content, data+1, txt_e-1);
11081405
else parse_inline(content, rndr, data+1, txt_e-1);
11091406
}
11101407
11111408
/* calling the relevant rendering function */
11121409
if( is_img ){
1113
- if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ) ob->nUsed--;
1410
+ if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ){
1411
+ ob->nUsed--;
1412
+ }
11141413
ret = rndr->make.image(ob, link, title, content, rndr->make.opaque);
1414
+ }else if( fn.nUsed ){
1415
+ if( rndr->make.footnote_ref ){
1416
+ ret = rndr->make.footnote_ref(ob, content, &fn.upc, fn.iMark,
1417
+ fn.nUsed, rndr->make.opaque);
1418
+ }
11151419
}else{
11161420
ret = rndr->make.link(ob, link, title, content, rndr->make.opaque);
11171421
}
11181422
11191423
/* cleanup */
@@ -1121,11 +1425,10 @@
11211425
release_work_buffer(rndr, title);
11221426
release_work_buffer(rndr, link);
11231427
release_work_buffer(rndr, content);
11241428
return ret ? i : 0;
11251429
}
1126
-
11271430
11281431
11291432
/*********************************
11301433
* BLOCK-LEVEL PARSING FUNCTIONS *
11311434
*********************************/
@@ -1806,11 +2109,12 @@
18062109
/* the end of the block has been found */
18072110
if( strcmp(curtag->text,"html")==0 ){
18082111
/* Omit <html> tags */
18092112
enum mkd_autolink dummy;
18102113
int k = tag_length(data, size, &dummy);
1811
- blob_init(&work, data+k, i-(j+k));
2114
+ int sz = i - (j+k);
2115
+ if( sz>0 ) blob_init(&work, data+k, sz);
18122116
}else{
18132117
blob_init(&work, data, i);
18142118
}
18152119
if( rndr->make.blockhtml ){
18162120
rndr->make.blockhtml(ob, &work, rndr->make.opaque);
@@ -2013,11 +2317,13 @@
20132317
char *data, /* input text */
20142318
size_t size /* input text size */
20152319
){
20162320
size_t beg, end, i;
20172321
char *txt_data;
2018
- int has_table = (rndr->make.table
2322
+ int has_table;
2323
+ if( !size ) return;
2324
+ has_table = (rndr->make.table
20192325
&& rndr->make.table_row
20202326
&& rndr->make.table_cell
20212327
&& memchr(data, '|', size)!=0);
20222328
20232329
beg = 0;
@@ -2063,11 +2369,11 @@
20632369
* REFERENCE PARSING *
20642370
*********************/
20652371
20662372
/* is_ref -- returns whether a line is a reference or not */
20672373
static int is_ref(
2068
- char *data, /* input text */
2374
+ const char *data, /* input text */
20692375
size_t beg, /* offset of the beginning of the line */
20702376
size_t end, /* offset of the end of the text */
20712377
size_t *last, /* last character of the link */
20722378
struct Blob *refs /* array of link references */
20732379
){
@@ -2097,10 +2403,12 @@
20972403
i += beg;
20982404
20992405
/* id part: anything but a newline between brackets */
21002406
if( data[i]!='[' ) return 0;
21012407
i++;
2408
+ if( i>=end || data[i]=='^' ) return 0; /* see is_footnote() */
2409
+
21022410
id_offset = i;
21032411
while( i<end && data[i]!='\n' && data[i]!='\r' && data[i]!=']' ){ i++; }
21042412
if( i>=end || data[i]!=']' ) return 0;
21052413
id_end = i;
21062414
@@ -2125,10 +2433,11 @@
21252433
&& data[i]!='\n'
21262434
&& data[i]!='\r'
21272435
){
21282436
i += 1;
21292437
}
2438
+ /* TODO: maybe require both data[i-1]=='>' && data[link_offset-1]=='<' ? */
21302439
if( data[i-1]=='>' ) link_end = i-1; else link_end = i;
21312440
21322441
/* optional spacer: (space | tab)* (newline | '\'' | '"' | '(' ) */
21332442
while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; }
21342443
if( i<end
@@ -2184,34 +2493,168 @@
21842493
}
21852494
blob_append(refs, (char *)&lr, sizeof lr);
21862495
return 1;
21872496
}
21882497
2498
+/*********************
2499
+ * FOOTNOTE PARSING *
2500
+ *********************/
2501
+
2502
+/* is_footnote -- check if data holds a definition of a labeled footnote.
2503
+ * If so then append the corresponding element to `footnotes` array */
2504
+static int is_footnote(
2505
+ const char *data, /* input text */
2506
+ size_t beg, /* offset of the beginning of the line */
2507
+ size_t end, /* offset of the end of the text */
2508
+ size_t *last, /* last character of the link */
2509
+ struct Blob * footnotes
2510
+){
2511
+ size_t i, id_offset, id_end, upc_offset, upc_size;
2512
+ struct footnote fn = FOOTNOTE_INITIALIZER;
2513
+
2514
+ /* failfast if data is too short */
2515
+ if( beg+5>=end ) return 0;
2516
+ i = beg;
2517
+
2518
+ /* footnote definition must start at the begining of a line */
2519
+ if( data[i]!='[' ) return 0;
2520
+ i++;
2521
+ if( data[i]!='^' ) return 0;
2522
+ id_offset = ++i;
2523
+
2524
+ /* id part: anything but a newline between brackets */
2525
+ while( i<end && data[i]!=']' && data[i]!='\n' && data[i]!='\r' ){ i++; }
2526
+ if( i>=end || data[i]!=']' ) return 0;
2527
+ id_end = i++;
2528
+
2529
+ /* spacer: colon (space | tab)* */
2530
+ if( i>=end || data[i]!=':' ) return 0;
2531
+ i++;
2532
+ while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; }
2533
+
2534
+ /* passthrough truncated footnote definition */
2535
+ if( i>=end ) return 0;
2536
+
2537
+ if( build_ref_id(&fn.id, data+id_offset, id_end-id_offset)<0 ) return 0;
2538
+
2539
+ /* footnote's text may start on the same line after [^id]: */
2540
+ upc_offset = upc_size = 0;
2541
+ if( data[i]!='\n' && data[i]!='\r' ){
2542
+ size_t j;
2543
+ upc_size = is_footnote_classlist(data+i, end-i, 1);
2544
+ upc_offset = i; /* prevent further checks for a classlist */
2545
+ i += upc_size;
2546
+ j = i;
2547
+ while( i<end && data[i]!='\n' && data[i]!='\r' ){ i++; };
2548
+ if( i!=j )blob_append(&fn.text, data+j, i-j);
2549
+ if( i<end ){
2550
+ blob_append_char(&fn.text, data[i]);
2551
+ i++;
2552
+ if( i<end && data[i]=='\n' && data[i-1]=='\r' ){
2553
+ blob_append_char(&fn.text, data[i]);
2554
+ i++;
2555
+ }
2556
+ }
2557
+ }else{
2558
+ i++;
2559
+ if( i<end && data[i]=='\n' && data[i-1]=='\r' ) i++;
2560
+ }
2561
+ if( i<end ){
2562
+
2563
+ /* compute the indentation from the 2nd line */
2564
+ size_t indent = i;
2565
+ const char *spaces = data+i;
2566
+ while( indent<end && data[indent]==' ' ){ indent++; }
2567
+ if( indent>=end ) goto footnote_finish;
2568
+ indent -= i;
2569
+ if( indent<2 ) goto footnote_finish;
21892570
2571
+ /* process the 2nd and subsequent lines */
2572
+ while( i+indent<end && memcmp(data+i,spaces,indent)==0 ){
2573
+ size_t j;
2574
+ i += indent;
2575
+ if( !upc_offset ){
2576
+ /* a classlist must be provided no later than at the 2nd line */
2577
+ upc_offset = i + sizeof_blank_prefix(data+i, end-i, 1);
2578
+ upc_size = is_footnote_classlist(data+upc_offset,
2579
+ end-upc_offset, 1);
2580
+ if( upc_size ){
2581
+ i = upc_offset + upc_size;
2582
+ }
2583
+ }
2584
+ j = i;
2585
+ while( i<end && data[i]!='\n' && data[i]!='\r' ){ i++; }
2586
+ if( i!=j ) blob_append(&fn.text, data+j, i-j);
2587
+ if( i>=end ) break;
2588
+ blob_append_char(&fn.text, data[i]);
2589
+ i++;
2590
+ if( i<end && data[i]=='\n' && data[i-1]=='\r' ){
2591
+ blob_append_char(&fn.text, data[i]);
2592
+ i++;
2593
+ }
2594
+ }
2595
+ }
2596
+footnote_finish:
2597
+ if( !blob_size(&fn.text) ){
2598
+ blob_reset(&fn.id);
2599
+ return 0;
2600
+ }
2601
+ if( !blob_trim(&fn.text) ){ /* if the content is all-blank */
2602
+ if( upc_size ){ /* interpret UPC as plain text */
2603
+ blob_append(&fn.text, data+upc_offset, upc_size);
2604
+ upc_size = 0;
2605
+ }else{
2606
+ blob_reset(&fn.id); /* or clean up and fail */
2607
+ blob_reset(&fn.text);
2608
+ return 0;
2609
+ }
2610
+ }
2611
+ /* a valid note has been found */
2612
+ if( last ) *last = i;
2613
+ if( footnotes ){
2614
+ fn.defno = COUNT_FOOTNOTES( footnotes );
2615
+ if( upc_size ){
2616
+ assert( upc_offset && upc_offset+upc_size<end );
2617
+ blob_append(&fn.upc, data+upc_offset, upc_size);
2618
+ }
2619
+ blob_append(footnotes, (char *)&fn, sizeof fn);
2620
+ }
2621
+ return 1;
2622
+}
21902623
21912624
/**********************
21922625
* EXPORTED FUNCTIONS *
21932626
**********************/
21942627
21952628
/* markdown -- parses the input buffer and renders it into the output buffer */
21962629
void markdown(
21972630
struct Blob *ob, /* output blob for rendered text */
2198
- struct Blob *ib, /* input blob in markdown */
2631
+ const struct Blob *ib, /* input blob in markdown */
21992632
const struct mkd_renderer *rndrer /* renderer descriptor (callbacks) */
22002633
){
22012634
struct link_ref *lr;
2635
+ struct footnote *fn;
22022636
size_t i, beg, end = 0;
22032637
struct render rndr;
2204
- char *ib_data;
2205
- Blob text = BLOB_INITIALIZER;
2638
+ Blob text = BLOB_INITIALIZER; /* input after the first pass */
2639
+ Blob * const allNotes = &rndr.notes.all;
22062640
22072641
/* filling the render structure */
22082642
if( !rndrer ) return;
22092643
rndr.make = *rndrer;
22102644
rndr.nBlobCache = 0;
22112645
rndr.iDepth = 0;
2212
- rndr.refs = empty_blob;
2646
+ rndr.refs = empty_blob;
2647
+ rndr.notes.all = empty_blob;
2648
+ rndr.notes.nMarks = 0;
2649
+ rndr.notes.misref.id = empty_blob;
2650
+ rndr.notes.misref.text = empty_blob;
2651
+ rndr.notes.misref.upc = empty_blob;
2652
+ rndr.notes.misref.bRndred = 0;
2653
+ rndr.notes.misref.nUsed = 0;
2654
+ rndr.notes.misref.iMark = -1;
2655
+
22132656
for(i=0; i<256; i++) rndr.active_char[i] = 0;
22142657
if( (rndr.make.emphasis
22152658
|| rndr.make.double_emphasis
22162659
|| rndr.make.triple_emphasis)
22172660
&& rndr.make.emph_chars
@@ -2221,32 +2664,34 @@
22212664
}
22222665
}
22232666
if( rndr.make.codespan ) rndr.active_char['`'] = char_codespan;
22242667
if( rndr.make.linebreak ) rndr.active_char['\n'] = char_linebreak;
22252668
if( rndr.make.image || rndr.make.link ) rndr.active_char['['] = char_link;
2669
+ if( rndr.make.footnote_ref ) rndr.active_char['('] = char_footnote;
22262670
rndr.active_char['<'] = char_langle_tag;
22272671
rndr.active_char['\\'] = char_escape;
22282672
rndr.active_char['&'] = char_entity;
22292673
2230
- /* first pass: looking for references, copying everything else */
2674
+ /* first pass: iterate over lines looking for references,
2675
+ * copying everything else into "text" */
22312676
beg = 0;
2232
- ib_data = blob_buffer(ib);
2233
- while( beg<blob_size(ib) ){ /* iterating over lines */
2234
- if( is_ref(ib_data, beg, blob_size(ib), &end, &rndr.refs) ){
2677
+ for(const size_t size = blob_size(ib); beg<size ;){
2678
+ const char* const data = blob_buffer(ib);
2679
+ if( is_ref(data, beg, size, &end, &rndr.refs) ){
2680
+ beg = end;
2681
+ }else if(is_footnote(data, beg, size, &end, &rndr.notes.all)){
22352682
beg = end;
22362683
}else{ /* skipping to the next line */
22372684
end = beg;
2238
- while( end<blob_size(ib) && ib_data[end]!='\n' && ib_data[end]!='\r' ){
2685
+ while( end<size && data[end]!='\n' && data[end]!='\r' ){
22392686
end += 1;
22402687
}
22412688
/* adding the line body if present */
2242
- if( end>beg ) blob_append(&text, ib_data + beg, end - beg);
2243
- while( end<blob_size(ib) && (ib_data[end]=='\n' || ib_data[end]=='\r') ){
2689
+ if( end>beg ) blob_append(&text, data + beg, end - beg);
2690
+ while( end<size && (data[end]=='\n' || data[end]=='\r') ){
22442691
/* add one \n per newline */
2245
- if( ib_data[end]=='\n'
2246
- || (end+1<blob_size(ib) && ib_data[end+1]!='\n')
2247
- ){
2692
+ if( data[end]=='\n' || (end+1<size && data[end+1]!='\n') ){
22482693
blob_append_char(&text, '\n');
22492694
}
22502695
end += 1;
22512696
}
22522697
beg = end;
@@ -2258,14 +2703,160 @@
22582703
qsort(blob_buffer(&rndr.refs),
22592704
blob_size(&rndr.refs)/sizeof(struct link_ref),
22602705
sizeof(struct link_ref),
22612706
cmp_link_ref_sort);
22622707
}
2708
+ rndr.notes.nLbled = COUNT_FOOTNOTES( allNotes );
2709
+
2710
+ /* sort footnotes by ID and join duplicates */
2711
+ if( rndr.notes.nLbled > 1 ){
2712
+ int nDups = 0;
2713
+ fn = CAST_AS_FOOTNOTES( allNotes );
2714
+ qsort(fn, rndr.notes.nLbled, sizeof(struct footnote), cmp_footnote_id);
2715
+
2716
+ /* concatenate footnotes with equal labels */
2717
+ for(i=0; i<rndr.notes.nLbled ;){
2718
+ struct footnote *x = fn + i;
2719
+ size_t j = i+1, k = blob_size(&x->text) + 64 + blob_size(&x->upc);
2720
+ while(j<rndr.notes.nLbled && !blob_compare(&x->id, &fn[j].id)){
2721
+ k += blob_size(&fn[j].text) + 10 + blob_size(&fn[j].upc);
2722
+ j++;
2723
+ nDups++;
2724
+ }
2725
+ if( i+1<j ){
2726
+ Blob list = empty_blob;
2727
+ blob_reserve(&list, k);
2728
+ /* must match _joined_footnote_indicator in html_footnote_item() */
2729
+ blob_append_literal(&list, "<ul class='fn-joined'>\n");
2730
+ for(k=i; k<j; k++){
2731
+ struct footnote *y = fn + k;
2732
+ blob_append_literal(&list, "<li>");
2733
+ if( blob_size(&y->upc) ){
2734
+ blob_appendb(&list, &y->upc);
2735
+ blob_reset(&y->upc);
2736
+ }
2737
+ blob_appendb(&list, &y->text);
2738
+ blob_append_literal(&list, "</li>\n");
2739
+
2740
+ /* free memory buffer */
2741
+ blob_reset(&y->text);
2742
+ if( k!=i ) blob_reset(&y->id);
2743
+ }
2744
+ blob_append_literal(&list, "</ul>\n");
2745
+ x->text = list;
2746
+ g.ftntsIssues[2]++;
2747
+ }
2748
+ i = j;
2749
+ }
2750
+ if( nDups ){ /* clean rndr.notes.all from invalidated footnotes */
2751
+ const int n = rndr.notes.nLbled - nDups;
2752
+ struct Blob filtered = empty_blob;
2753
+ blob_reserve(&filtered, n*sizeof(struct footnote));
2754
+ for(i=0; i<rndr.notes.nLbled; i++){
2755
+ if( blob_size(&fn[i].id) ){
2756
+ blob_append(&filtered, (char*)(fn+i), sizeof(struct footnote));
2757
+ }
2758
+ }
2759
+ blob_reset( allNotes );
2760
+ rndr.notes.all = filtered;
2761
+ rndr.notes.nLbled = n;
2762
+ assert( COUNT_FOOTNOTES(allNotes) == rndr.notes.nLbled );
2763
+ }
2764
+ }
2765
+ fn = CAST_AS_FOOTNOTES( allNotes );
2766
+ for(i=0; i<rndr.notes.nLbled; i++){
2767
+ fn[i].index = i;
2768
+ }
2769
+ assert( rndr.notes.nMarks==0 );
22632770
22642771
/* second pass: actual rendering */
22652772
if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
22662773
parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));
2774
+
2775
+ if( blob_size(allNotes) || rndr.notes.misref.nUsed ){
2776
+
2777
+ /* Footnotes must be parsed for the correct discovery of (back)links */
2778
+ Blob *notes = new_work_buffer( &rndr );
2779
+ if( blob_size(allNotes) ){
2780
+ Blob *tmp = new_work_buffer( &rndr );
2781
+ int nMarks = -1, maxDepth = 5;
2782
+
2783
+ /* inline notes may get appended to rndr.notes.all while rendering */
2784
+ while(1){
2785
+ struct footnote *aNotes;
2786
+ const int N = COUNT_FOOTNOTES( allNotes );
2787
+
2788
+ /* make a shallow copy of `allNotes` */
2789
+ blob_truncate(notes,0);
2790
+ blob_appendb(notes, allNotes);
2791
+ aNotes = CAST_AS_FOOTNOTES(notes);
2792
+ qsort(aNotes, N, sizeof(struct footnote), cmp_footnote_sort);
2793
+
2794
+ if( --maxDepth < 0 || nMarks == rndr.notes.nMarks ) break;
2795
+ nMarks = rndr.notes.nMarks;
2796
+
2797
+ for(i=0; i<N; i++){
2798
+ const int j = aNotes[i].index;
2799
+ struct footnote *x = CAST_AS_FOOTNOTES(allNotes) + j;
2800
+ assert( 0<=j && j<N );
2801
+ if( x->bRndred || !x->nUsed ) continue;
2802
+ assert( x->iMark > 0 );
2803
+ assert( blob_size(&x->text) );
2804
+ blob_truncate(tmp,0);
2805
+
2806
+ /* `allNotes` may be altered and extended through this call */
2807
+ parse_inline(tmp, &rndr, blob_buffer(&x->text), blob_size(&x->text));
2808
+
2809
+ x = CAST_AS_FOOTNOTES(allNotes) + j;
2810
+ blob_truncate(&x->text,0);
2811
+ blob_appendb(&x->text, tmp);
2812
+ x->bRndred = 1;
2813
+ }
2814
+ }
2815
+ release_work_buffer(&rndr,tmp);
2816
+ }
2817
+
2818
+ /* footnotes rendering */
2819
+ if( rndr.make.footnote_item && rndr.make.footnotes ){
2820
+ Blob *all_items = new_work_buffer(&rndr);
2821
+ int j = -1;
2822
+
2823
+ /* Assert that the in-memory layout of id, text and upc within
2824
+ ** footnote struct matches the expectations of html_footnote_item()
2825
+ ** If it doesn't then a compiler has done something very weird.
2826
+ */
2827
+ assert( &(rndr.notes.misref.id) == &(rndr.notes.misref.text) - 1 );
2828
+ assert( &(rndr.notes.misref.upc) == &(rndr.notes.misref.text) + 1 );
2829
+
2830
+ for(i=0; i<COUNT_FOOTNOTES(notes); i++){
2831
+ const struct footnote* x = CAST_AS_FOOTNOTES(notes) + i;
2832
+ const int xUsed = x->bRndred ? x->nUsed : 0;
2833
+ if( !x->iMark ) break;
2834
+ assert( x->nUsed );
2835
+ rndr.make.footnote_item(all_items, &x->text, x->iMark,
2836
+ xUsed, rndr.make.opaque);
2837
+ if( !xUsed ) g.ftntsIssues[3]++; /* an overnested footnote */
2838
+ j = i;
2839
+ }
2840
+ if( rndr.notes.misref.nUsed ){
2841
+ rndr.make.footnote_item(all_items, 0, -1,
2842
+ rndr.notes.misref.nUsed, rndr.make.opaque);
2843
+ g.ftntsIssues[0] += rndr.notes.misref.nUsed;
2844
+ }
2845
+ while( ++j < COUNT_FOOTNOTES(notes) ){
2846
+ const struct footnote* x = CAST_AS_FOOTNOTES(notes) + j;
2847
+ assert( !x->iMark );
2848
+ assert( !x->nUsed );
2849
+ assert( !x->bRndred );
2850
+ rndr.make.footnote_item(all_items,&x->text,0,0,rndr.make.opaque);
2851
+ g.ftntsIssues[1]++;
2852
+ }
2853
+ rndr.make.footnotes(ob, all_items, rndr.make.opaque);
2854
+ release_work_buffer(&rndr, all_items);
2855
+ }
2856
+ release_work_buffer(&rndr, notes);
2857
+ }
22672858
if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);
22682859
22692860
/* clean-up */
22702861
assert( rndr.iDepth==0 );
22712862
blob_reset(&text);
@@ -2275,9 +2866,17 @@
22752866
blob_reset(&lr[i].id);
22762867
blob_reset(&lr[i].link);
22772868
blob_reset(&lr[i].title);
22782869
}
22792870
blob_reset(&rndr.refs);
2871
+ fn = CAST_AS_FOOTNOTES( allNotes );
2872
+ end = COUNT_FOOTNOTES( allNotes );
2873
+ for(i=0; i<end; i++){
2874
+ if(blob_size(&fn[i].id)) blob_reset(&fn[i].id);
2875
+ if(blob_size(&fn[i].upc)) blob_reset(&fn[i].upc);
2876
+ blob_reset(&fn[i].text);
2877
+ }
2878
+ blob_reset(&rndr.notes.all);
22802879
for(i=0; i<rndr.nBlobCache; i++){
22812880
fossil_free(rndr.aBlobCache[i]);
22822881
}
22832882
}
22842883
--- src/markdown.c
+++ src/markdown.c
@@ -45,10 +45,11 @@
45 /* mkd_renderer -- functions for rendering parsed data */
46 struct mkd_renderer {
47 /* document level callbacks */
48 void (*prolog)(struct Blob *ob, void *opaque);
49 void (*epilog)(struct Blob *ob, void *opaque);
 
50
51 /* block level callbacks - NULL skips the block */
52 void (*blockcode)(struct Blob *ob, struct Blob *text, void *opaque);
53 void (*blockquote)(struct Blob *ob, struct Blob *text, void *opaque);
54 void (*blockhtml)(struct Blob *ob, struct Blob *text, void *opaque);
@@ -63,10 +64,12 @@
63 void *opaque);
64 void (*table_cell)(struct Blob *ob, struct Blob *text, int flags,
65 void *opaque);
66 void (*table_row)(struct Blob *ob, struct Blob *cells, int flags,
67 void *opaque);
 
 
68
69 /* span level callbacks - NULL or return 0 prints the span verbatim */
70 int (*autolink)(struct Blob *ob, struct Blob *link,
71 enum mkd_autolink type, void *opaque);
72 int (*codespan)(struct Blob *ob, struct Blob *text, int nSep, void *opaque);
@@ -79,10 +82,12 @@
79 int (*link)(struct Blob *ob, struct Blob *link, struct Blob *title,
80 struct Blob *content, void *opaque);
81 int (*raw_html_tag)(struct Blob *ob, struct Blob *tag, void *opaque);
82 int (*triple_emphasis)(struct Blob *ob, struct Blob *text,
83 char c, void *opaque);
 
 
84
85 /* low level callbacks - NULL copies input directly into the output */
86 void (*entity)(struct Blob *ob, struct Blob *entity, void *opaque);
87 void (*normal_text)(struct Blob *ob, struct Blob *text, void *opaque);
88
@@ -113,31 +118,54 @@
113
114 /**********************
115 * EXPORTED FUNCTIONS *
116 **********************/
117
118 /* markdown -- parses the input buffer and renders it into the output buffer */
 
 
119 void markdown(
120 struct Blob *ob,
121 struct Blob *ib,
122 const struct mkd_renderer *rndr);
123
124
125 #endif /* INTERFACE */
126
 
 
 
127
128 /***************
129 * LOCAL TYPES *
130 ***************/
131
132 /* link_ref -- reference to a link */
 
 
133 struct link_ref {
134 struct Blob id;
135 struct Blob link;
136 struct Blob title;
137 };
138
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
140 /* char_trigger -- function pointer to render active chars */
141 /* returns the number of chars taken care of */
142 /* data is the pointer of the beginning of the span */
143 /* offset is the number of valid chars before data */
@@ -156,12 +184,19 @@
156 struct Blob refs;
157 char_trigger active_char[256];
158 int iDepth; /* Depth of recursion */
159 int nBlobCache; /* Number of entries in aBlobCache */
160 struct Blob *aBlobCache[20]; /* Cache of Blobs available for reuse */
 
 
 
 
 
 
 
 
161 };
162
163
164 /* html_tag -- structure for quick HTML tag search (inspired from discount) */
165 struct html_tag {
166 const char *text;
167 int size;
@@ -183,16 +218,18 @@
183 { "html", 4 },
184 { "pre", 3 },
185 { "script", 6 },
186 };
187
188
189 /***************************
190 * STATIC HELPER FUNCTIONS *
191 ***************************/
192
193 /* build_ref_id -- collapse whitespace from input text to make it a ref id */
 
 
 
194 static int build_ref_id(struct Blob *id, const char *data, size_t size){
195 size_t beg, i;
196 char *id_data;
197
198 /* skip leading whitespace */
@@ -247,10 +284,58 @@
247 struct link_ref *lra = (void *)a;
248 struct link_ref *lrb = (void *)b;
249 return blob_compare(&lra->id, &lrb->id);
250 }
251
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
253 /* cmp_html_tag -- comparison function for bsearch() (stolen from discount) */
254 static int cmp_html_tag(const void *a, const void *b){
255 const struct html_tag *hta = a;
256 const struct html_tag *htb = b;
@@ -579,12 +664,14 @@
579 if( fossil_isalnum(after) ) return 0;
580 return 1;
581 }
582
583
584 /* parse_emph1 -- parsing single emphasis */
585 /* closed by a symbol not preceded by whitespace and not followed by symbol */
 
 
586 static size_t parse_emph1(
587 struct Blob *ob,
588 struct render *rndr,
589 char *data,
590 size_t size,
@@ -624,12 +711,13 @@
624 }
625 }
626 return 0;
627 }
628
629
630 /* parse_emph2 -- parsing single emphasis */
 
631 static size_t parse_emph2(
632 struct Blob *ob,
633 struct render *rndr,
634 char *data,
635 size_t size,
@@ -663,13 +751,14 @@
663 i++;
664 }
665 return 0;
666 }
667
668
669 /* parse_emph3 -- parsing single emphasis */
670 /* finds the first closing tag, and delegates to the other emph */
 
671 static size_t parse_emph3(
672 struct Blob *ob,
673 struct render *rndr,
674 char *data,
675 size_t size,
@@ -711,12 +800,13 @@
711 }
712 }
713 return 0;
714 }
715
716
717 /* char_emphasis -- single and double emphasis parsing */
 
718 static size_t char_emphasis(
719 struct Blob *ob,
720 struct render *rndr,
721 char *data,
722 size_t offset,
@@ -756,12 +846,13 @@
756 return ret+3;
757 }
758 return 0;
759 }
760
761
762 /* char_linebreak -- '\n' preceded by two spaces (assuming linebreak != 0) */
 
763 static size_t char_linebreak(
764 struct Blob *ob,
765 struct render *rndr,
766 char *data,
767 size_t offset,
@@ -771,12 +862,13 @@
771 /* removing the last space from ob and rendering */
772 if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]==' ' ) ob->nUsed--;
773 return rndr->make.linebreak(ob, rndr->make.opaque) ? 1 : 0;
774 }
775
776
777 /* char_codespan -- '`' parsing a code span (assuming codespan != 0) */
 
778 static size_t char_codespan(
779 struct Blob *ob,
780 struct render *rndr,
781 char *data,
782 size_t offset,
@@ -813,11 +905,13 @@
813 }
814 return end;
815 }
816
817
818 /* char_escape -- '\\' backslash escape */
 
 
819 static size_t char_escape(
820 struct Blob *ob,
821 struct render *rndr,
822 char *data,
823 size_t offset,
@@ -833,13 +927,14 @@
833 }
834 }
835 return 2;
836 }
837
838
839 /* char_entity -- '&' escaped when it doesn't belong to an entity */
840 /* valid entities are assumed to be anything matching &#?[A-Za-z0-9]+; */
 
841 static size_t char_entity(
842 struct Blob *ob,
843 struct render *rndr,
844 char *data,
845 size_t offset,
@@ -869,12 +964,13 @@
869 blob_append(ob, data, end);
870 }
871 return end;
872 }
873
874
875 /* char_langle_tag -- '<' when tags or autolinks are allowed */
 
876 static size_t char_langle_tag(
877 struct Blob *ob,
878 struct render *rndr,
879 char *data,
880 size_t offset,
@@ -899,12 +995,12 @@
899 }else{
900 return end;
901 }
902 }
903
904
905 /* get_link_inline -- extract inline-style link and title from
906 ** parenthesed data
907 */
908 static int get_link_inline(
909 struct Blob *link,
910 struct Blob *title,
@@ -954,11 +1050,11 @@
954 link_e--;
955 }
956
957 /* remove optional angle brackets around the link */
958 if( data[link_b]=='<' ) link_b += 1;
959 if( data[link_e-1]=='>' ) link_e -= 1;
960
961 /* escape backslashed character from link */
962 blob_reset(link);
963 i = link_b;
964 while( i<link_e ){
@@ -975,145 +1071,353 @@
975 /* this function always succeed */
976 return 0;
977 }
978
979
980 /* get_link_ref -- extract referenced link and title from id */
 
 
981 static int get_link_ref(
982 struct render *rndr,
983 struct Blob *link,
984 struct Blob *title,
985 char *data,
986 size_t size
987 ){
988 struct link_ref *lr;
 
989
990 /* find the link from its id (stored temporarily in link) */
991 blob_reset(link);
992 if( build_ref_id(link, data, size)<0 ) return -1;
993 lr = bsearch(link,
994 blob_buffer(&rndr->refs),
995 blob_size(&rndr->refs)/sizeof(struct link_ref),
996 sizeof (struct link_ref),
997 cmp_link_ref);
998 if( !lr ) return -1;
999
1000 /* fill the output buffers */
1001 blob_reset(link);
1002 blob_reset(title);
1003 blob_append(link, blob_buffer(&lr->link), blob_size(&lr->link));
1004 blob_append(title, blob_buffer(&lr->title), blob_size(&lr->title));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1005 return 0;
1006 }
1007
 
 
 
 
 
 
 
 
 
 
 
 
1008
1009 /* char_link -- '[': parsing a link or an image */
 
 
 
 
 
 
 
 
 
 
 
 
1010 static size_t char_link(
1011 struct Blob *ob,
1012 struct render *rndr,
1013 char *data,
1014 size_t offset,
1015 size_t size
1016 ){
1017 int is_img = (offset && data[-1] == '!'), level;
1018 size_t i = 1, txt_e;
1019 struct Blob *content = 0;
1020 struct Blob *link = 0;
1021 struct Blob *title = 0;
 
1022 int ret;
1023
1024 /* checking whether the correct renderer exists */
1025 if( (is_img && !rndr->make.image) || (!is_img && !rndr->make.link) ){
1026 return 0;
1027 }
1028
1029 /* looking for the matching closing bracket */
1030 for(level=1; i<size; i++){
1031 if( data[i]=='\n' ) /* do nothing */;
1032 else if( data[i-1]=='\\' ) continue;
1033 else if( data[i]=='[' ) level += 1;
1034 else if( data[i]==']' ){
1035 level--;
1036 if( level<=0 ) break;
1037 }
1038 }
1039 if( i>=size ) return 0;
1040 txt_e = i;
1041 i++;
1042
1043 /* skip any amount of whitespace or newline */
1044 /* (this is much more laxist than original markdown syntax) */
1045 while( i<size && (data[i]==' ' || data[i]=='\t' || data[i]=='\n') ){ i++; }
1046
1047 /* allocate temporary buffers to store content, link and title */
1048 title = new_work_buffer(rndr);
1049 content = new_work_buffer(rndr);
1050 link = new_work_buffer(rndr);
1051 ret = 0; /* error if we don't get to the callback */
1052
1053 /* inline style link */
1054 if( i<size && data[i]=='(' ){
1055 size_t span_end = i;
1056 while( span_end<size
1057 && !(data[span_end]==')' && (span_end==i || data[span_end-1]!='\\'))
1058 ){
1059 span_end++;
1060 }
1061
1062 if( span_end>=size
1063 || get_link_inline(link, title, data+i+1, span_end-(i+1))<0
1064 ){
1065 goto char_link_cleanup;
1066 }
1067
1068 i = span_end+1;
1069
1070 /* reference style link */
1071 }else if( i<size && data[i]=='[' ){
1072 char *id_data;
1073 size_t id_size, id_end = i;
1074
1075 while( id_end<size && data[id_end]!=']' ){ id_end++; }
1076
1077 if( id_end>=size ) goto char_link_cleanup;
1078
1079 if( i+1==id_end ){
1080 /* implicit id - use the contents */
1081 id_data = data+1;
1082 id_size = txt_e-1;
1083 }else{
1084 /* explicit id - between brackets */
1085 id_data = data+i+1;
1086 id_size = id_end-(i+1);
1087 }
1088
1089 if( get_link_ref(rndr, link, title, id_data, id_size)<0 ){
1090 goto char_link_cleanup;
1091 }
1092
1093 i = id_end+1;
1094
1095 /* shortcut reference style link */
1096 }else{
1097 if( get_link_ref(rndr, link, title, data+1, txt_e-1)<0 ){
1098 goto char_link_cleanup;
1099 }
1100
1101 /* rewinding the whitespace */
1102 i = txt_e+1;
1103 }
1104
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1105 /* building content: img alt is escaped, link content is parsed */
1106 if( txt_e>1 ){
1107 if( is_img ) blob_append(content, data+1, txt_e-1);
1108 else parse_inline(content, rndr, data+1, txt_e-1);
1109 }
1110
1111 /* calling the relevant rendering function */
1112 if( is_img ){
1113 if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ) ob->nUsed--;
 
 
1114 ret = rndr->make.image(ob, link, title, content, rndr->make.opaque);
 
 
 
 
 
1115 }else{
1116 ret = rndr->make.link(ob, link, title, content, rndr->make.opaque);
1117 }
1118
1119 /* cleanup */
@@ -1121,11 +1425,10 @@
1121 release_work_buffer(rndr, title);
1122 release_work_buffer(rndr, link);
1123 release_work_buffer(rndr, content);
1124 return ret ? i : 0;
1125 }
1126
1127
1128
1129 /*********************************
1130 * BLOCK-LEVEL PARSING FUNCTIONS *
1131 *********************************/
@@ -1806,11 +2109,12 @@
1806 /* the end of the block has been found */
1807 if( strcmp(curtag->text,"html")==0 ){
1808 /* Omit <html> tags */
1809 enum mkd_autolink dummy;
1810 int k = tag_length(data, size, &dummy);
1811 blob_init(&work, data+k, i-(j+k));
 
1812 }else{
1813 blob_init(&work, data, i);
1814 }
1815 if( rndr->make.blockhtml ){
1816 rndr->make.blockhtml(ob, &work, rndr->make.opaque);
@@ -2013,11 +2317,13 @@
2013 char *data, /* input text */
2014 size_t size /* input text size */
2015 ){
2016 size_t beg, end, i;
2017 char *txt_data;
2018 int has_table = (rndr->make.table
 
 
2019 && rndr->make.table_row
2020 && rndr->make.table_cell
2021 && memchr(data, '|', size)!=0);
2022
2023 beg = 0;
@@ -2063,11 +2369,11 @@
2063 * REFERENCE PARSING *
2064 *********************/
2065
2066 /* is_ref -- returns whether a line is a reference or not */
2067 static int is_ref(
2068 char *data, /* input text */
2069 size_t beg, /* offset of the beginning of the line */
2070 size_t end, /* offset of the end of the text */
2071 size_t *last, /* last character of the link */
2072 struct Blob *refs /* array of link references */
2073 ){
@@ -2097,10 +2403,12 @@
2097 i += beg;
2098
2099 /* id part: anything but a newline between brackets */
2100 if( data[i]!='[' ) return 0;
2101 i++;
 
 
2102 id_offset = i;
2103 while( i<end && data[i]!='\n' && data[i]!='\r' && data[i]!=']' ){ i++; }
2104 if( i>=end || data[i]!=']' ) return 0;
2105 id_end = i;
2106
@@ -2125,10 +2433,11 @@
2125 && data[i]!='\n'
2126 && data[i]!='\r'
2127 ){
2128 i += 1;
2129 }
 
2130 if( data[i-1]=='>' ) link_end = i-1; else link_end = i;
2131
2132 /* optional spacer: (space | tab)* (newline | '\'' | '"' | '(' ) */
2133 while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; }
2134 if( i<end
@@ -2184,34 +2493,168 @@
2184 }
2185 blob_append(refs, (char *)&lr, sizeof lr);
2186 return 1;
2187 }
2188
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2189
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2190
2191 /**********************
2192 * EXPORTED FUNCTIONS *
2193 **********************/
2194
2195 /* markdown -- parses the input buffer and renders it into the output buffer */
2196 void markdown(
2197 struct Blob *ob, /* output blob for rendered text */
2198 struct Blob *ib, /* input blob in markdown */
2199 const struct mkd_renderer *rndrer /* renderer descriptor (callbacks) */
2200 ){
2201 struct link_ref *lr;
 
2202 size_t i, beg, end = 0;
2203 struct render rndr;
2204 char *ib_data;
2205 Blob text = BLOB_INITIALIZER;
2206
2207 /* filling the render structure */
2208 if( !rndrer ) return;
2209 rndr.make = *rndrer;
2210 rndr.nBlobCache = 0;
2211 rndr.iDepth = 0;
2212 rndr.refs = empty_blob;
 
 
 
 
 
 
 
 
 
2213 for(i=0; i<256; i++) rndr.active_char[i] = 0;
2214 if( (rndr.make.emphasis
2215 || rndr.make.double_emphasis
2216 || rndr.make.triple_emphasis)
2217 && rndr.make.emph_chars
@@ -2221,32 +2664,34 @@
2221 }
2222 }
2223 if( rndr.make.codespan ) rndr.active_char['`'] = char_codespan;
2224 if( rndr.make.linebreak ) rndr.active_char['\n'] = char_linebreak;
2225 if( rndr.make.image || rndr.make.link ) rndr.active_char['['] = char_link;
 
2226 rndr.active_char['<'] = char_langle_tag;
2227 rndr.active_char['\\'] = char_escape;
2228 rndr.active_char['&'] = char_entity;
2229
2230 /* first pass: looking for references, copying everything else */
 
2231 beg = 0;
2232 ib_data = blob_buffer(ib);
2233 while( beg<blob_size(ib) ){ /* iterating over lines */
2234 if( is_ref(ib_data, beg, blob_size(ib), &end, &rndr.refs) ){
 
 
2235 beg = end;
2236 }else{ /* skipping to the next line */
2237 end = beg;
2238 while( end<blob_size(ib) && ib_data[end]!='\n' && ib_data[end]!='\r' ){
2239 end += 1;
2240 }
2241 /* adding the line body if present */
2242 if( end>beg ) blob_append(&text, ib_data + beg, end - beg);
2243 while( end<blob_size(ib) && (ib_data[end]=='\n' || ib_data[end]=='\r') ){
2244 /* add one \n per newline */
2245 if( ib_data[end]=='\n'
2246 || (end+1<blob_size(ib) && ib_data[end+1]!='\n')
2247 ){
2248 blob_append_char(&text, '\n');
2249 }
2250 end += 1;
2251 }
2252 beg = end;
@@ -2258,14 +2703,160 @@
2258 qsort(blob_buffer(&rndr.refs),
2259 blob_size(&rndr.refs)/sizeof(struct link_ref),
2260 sizeof(struct link_ref),
2261 cmp_link_ref_sort);
2262 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2263
2264 /* second pass: actual rendering */
2265 if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
2266 parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2267 if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);
2268
2269 /* clean-up */
2270 assert( rndr.iDepth==0 );
2271 blob_reset(&text);
@@ -2275,9 +2866,17 @@
2275 blob_reset(&lr[i].id);
2276 blob_reset(&lr[i].link);
2277 blob_reset(&lr[i].title);
2278 }
2279 blob_reset(&rndr.refs);
 
 
 
 
 
 
 
 
2280 for(i=0; i<rndr.nBlobCache; i++){
2281 fossil_free(rndr.aBlobCache[i]);
2282 }
2283 }
2284
--- src/markdown.c
+++ src/markdown.c
@@ -45,10 +45,11 @@
45 /* mkd_renderer -- functions for rendering parsed data */
46 struct mkd_renderer {
47 /* document level callbacks */
48 void (*prolog)(struct Blob *ob, void *opaque);
49 void (*epilog)(struct Blob *ob, void *opaque);
50 void (*footnotes)(struct Blob *ob, const struct Blob *items, void *opaque);
51
52 /* block level callbacks - NULL skips the block */
53 void (*blockcode)(struct Blob *ob, struct Blob *text, void *opaque);
54 void (*blockquote)(struct Blob *ob, struct Blob *text, void *opaque);
55 void (*blockhtml)(struct Blob *ob, struct Blob *text, void *opaque);
@@ -63,10 +64,12 @@
64 void *opaque);
65 void (*table_cell)(struct Blob *ob, struct Blob *text, int flags,
66 void *opaque);
67 void (*table_row)(struct Blob *ob, struct Blob *cells, int flags,
68 void *opaque);
69 void (*footnote_item)(struct Blob *ob, const struct Blob *text,
70 int index, int nUsed, void *opaque);
71
72 /* span level callbacks - NULL or return 0 prints the span verbatim */
73 int (*autolink)(struct Blob *ob, struct Blob *link,
74 enum mkd_autolink type, void *opaque);
75 int (*codespan)(struct Blob *ob, struct Blob *text, int nSep, void *opaque);
@@ -79,10 +82,12 @@
82 int (*link)(struct Blob *ob, struct Blob *link, struct Blob *title,
83 struct Blob *content, void *opaque);
84 int (*raw_html_tag)(struct Blob *ob, struct Blob *tag, void *opaque);
85 int (*triple_emphasis)(struct Blob *ob, struct Blob *text,
86 char c, void *opaque);
87 int (*footnote_ref)(struct Blob *ob, const struct Blob *span,
88 const struct Blob *upc, int index, int locus, void *opaque);
89
90 /* low level callbacks - NULL copies input directly into the output */
91 void (*entity)(struct Blob *ob, struct Blob *entity, void *opaque);
92 void (*normal_text)(struct Blob *ob, struct Blob *text, void *opaque);
93
@@ -113,31 +118,54 @@
118
119 /**********************
120 * EXPORTED FUNCTIONS *
121 **********************/
122
123 /*
124 ** markdown -- parses the input buffer and renders it into the output buffer.
125 */
126 void markdown(
127 struct Blob *ob,
128 const struct Blob *ib,
129 const struct mkd_renderer *rndr);
130
131
132 #endif /* INTERFACE */
133
134 #define BLOB_COUNT(pBlob,el_type) (blob_size(pBlob)/sizeof(el_type))
135 #define COUNT_FOOTNOTES(pBlob) BLOB_COUNT(pBlob,struct footnote)
136 #define CAST_AS_FOOTNOTES(pBlob) ((struct footnote*)blob_buffer(pBlob))
137
138 /***************
139 * LOCAL TYPES *
140 ***************/
141
142 /*
143 ** link_ref -- reference to a link.
144 */
145 struct link_ref {
146 struct Blob id; /* must be the first field as in footnote struct */
147 struct Blob link;
148 struct Blob title;
149 };
150
151 /*
152 ** A footnote's data.
153 ** id, text, and upc fields must be in that particular order.
154 */
155 struct footnote {
156 struct Blob id; /* must be the first field as in link_ref struct */
157 struct Blob text; /* footnote's content that is rendered at the end */
158 struct Blob upc; /* user-provided classes .ASCII-alnum.or-hypen: */
159 int bRndred; /* indicates if `text` holds a rendered content */
160
161 int defno; /* serial number of definition, set during the first pass */
162 int index; /* set to the index within array after ordering by id */
163 int iMark; /* user-visible numeric marker, assigned upon the first use*/
164 int nUsed; /* counts references to this note, increments upon each use*/
165 };
166 #define FOOTNOTE_INITIALIZER {empty_blob,empty_blob,empty_blob, 0,0,0,0,0}
167
168 /* char_trigger -- function pointer to render active chars */
169 /* returns the number of chars taken care of */
170 /* data is the pointer of the beginning of the span */
171 /* offset is the number of valid chars before data */
@@ -156,12 +184,19 @@
184 struct Blob refs;
185 char_trigger active_char[256];
186 int iDepth; /* Depth of recursion */
187 int nBlobCache; /* Number of entries in aBlobCache */
188 struct Blob *aBlobCache[20]; /* Cache of Blobs available for reuse */
189
190 struct {
191 Blob all; /* Buffer that holds array of footnotes. Its underline
192 memory may be reallocated when a new footnote is added. */
193 int nLbled; /* number of labeled footnotes found during the first pass */
194 int nMarks; /* counts distinct indices found during the second pass */
195 struct footnote misref; /* nUsed counts misreferences, iMark must be -1 */
196 } notes;
197 };
 
198
199 /* html_tag -- structure for quick HTML tag search (inspired from discount) */
200 struct html_tag {
201 const char *text;
202 int size;
@@ -183,16 +218,18 @@
218 { "html", 4 },
219 { "pre", 3 },
220 { "script", 6 },
221 };
222
 
223 /***************************
224 * STATIC HELPER FUNCTIONS *
225 ***************************/
226
227 /*
228 ** build_ref_id -- collapse whitespace from input text to make it a ref id.
229 ** Potential TODO: maybe also handle CR+LF line endings?
230 */
231 static int build_ref_id(struct Blob *id, const char *data, size_t size){
232 size_t beg, i;
233 char *id_data;
234
235 /* skip leading whitespace */
@@ -247,10 +284,58 @@
284 struct link_ref *lra = (void *)a;
285 struct link_ref *lrb = (void *)b;
286 return blob_compare(&lra->id, &lrb->id);
287 }
288
289 /*
290 ** cmp_footnote_id -- comparison function for footnotes qsort.
291 ** Empty IDs sort last (in undetermined order).
292 ** Equal IDs are sorted in the order of definition in the source.
293 */
294 static int cmp_footnote_id(const void *fna, const void *fnb){
295 const struct footnote *a = fna, *b = fnb;
296 const int szA = blob_size(&a->id), szB = blob_size(&b->id);
297 if( szA ){
298 if( szB ){
299 int cmp = blob_compare(&a->id, &b->id);
300 if( cmp ) return cmp;
301 }else return -1;
302 }else return szB ? 1 : 0;
303 /* IDs are equal and non-empty */
304 if( a->defno < b->defno ) return -1;
305 if( a->defno > b->defno ) return 1;
306 assert(!"reachable");
307 return 0; /* should never reach here */
308 }
309
310 /*
311 ** cmp_footnote_sort -- comparison function for footnotes qsort.
312 ** Unreferenced footnotes (when nUsed == 0) sort last and
313 ** are sorted in the order of definition in the source.
314 */
315 static int cmp_footnote_sort(const void *fna, const void *fnb){
316 const struct footnote *a = fna, *b = fnb;
317 int i, j;
318 assert( a->nUsed >= 0 );
319 assert( b->nUsed >= 0 );
320 assert( a->defno >= 0 );
321 assert( b->defno >= 0 );
322 if( a->nUsed ){
323 assert( a->iMark > 0 );
324 if( !b->nUsed ) return -1;
325 assert( b->iMark > 0 );
326 i = a->iMark;
327 j = b->iMark;
328 }else{
329 if( b->nUsed ) return 1;
330 i = a->defno;
331 j = b->defno;
332 }
333 if( i < j ) return -1;
334 if( i > j ) return 1;
335 return 0;
336 }
337
338 /* cmp_html_tag -- comparison function for bsearch() (stolen from discount) */
339 static int cmp_html_tag(const void *a, const void *b){
340 const struct html_tag *hta = a;
341 const struct html_tag *htb = b;
@@ -579,12 +664,14 @@
664 if( fossil_isalnum(after) ) return 0;
665 return 1;
666 }
667
668
669 /*
670 ** parse_emph1 -- parsing single emphasis.
671 ** closed by a symbol not preceded by whitespace and not followed by symbol.
672 */
673 static size_t parse_emph1(
674 struct Blob *ob,
675 struct render *rndr,
676 char *data,
677 size_t size,
@@ -624,12 +711,13 @@
711 }
712 }
713 return 0;
714 }
715
716 /*
717 ** parse_emph2 -- parsing single emphasis.
718 */
719 static size_t parse_emph2(
720 struct Blob *ob,
721 struct render *rndr,
722 char *data,
723 size_t size,
@@ -663,13 +751,14 @@
751 i++;
752 }
753 return 0;
754 }
755
756 /*
757 ** parse_emph3 -- parsing single emphasis.
758 ** finds the first closing tag, and delegates to the other emph.
759 */
760 static size_t parse_emph3(
761 struct Blob *ob,
762 struct render *rndr,
763 char *data,
764 size_t size,
@@ -711,12 +800,13 @@
800 }
801 }
802 return 0;
803 }
804
805 /*
806 ** char_emphasis -- single and double emphasis parsing.
807 */
808 static size_t char_emphasis(
809 struct Blob *ob,
810 struct render *rndr,
811 char *data,
812 size_t offset,
@@ -756,12 +846,13 @@
846 return ret+3;
847 }
848 return 0;
849 }
850
851 /*
852 ** char_linebreak -- '\n' preceded by two spaces (assuming linebreak != 0).
853 */
854 static size_t char_linebreak(
855 struct Blob *ob,
856 struct render *rndr,
857 char *data,
858 size_t offset,
@@ -771,12 +862,13 @@
862 /* removing the last space from ob and rendering */
863 if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]==' ' ) ob->nUsed--;
864 return rndr->make.linebreak(ob, rndr->make.opaque) ? 1 : 0;
865 }
866
867 /*
868 ** char_codespan -- '`' parsing a code span (assuming codespan != 0).
869 */
870 static size_t char_codespan(
871 struct Blob *ob,
872 struct render *rndr,
873 char *data,
874 size_t offset,
@@ -813,11 +905,13 @@
905 }
906 return end;
907 }
908
909
910 /*
911 ** char_escape -- '\\' backslash escape.
912 */
913 static size_t char_escape(
914 struct Blob *ob,
915 struct render *rndr,
916 char *data,
917 size_t offset,
@@ -833,13 +927,14 @@
927 }
928 }
929 return 2;
930 }
931
932 /*
933 ** char_entity -- '&' escaped when it doesn't belong to an entity.
934 ** valid entities are assumed to be anything matching &#?[A-Za-z0-9]+;
935 */
936 static size_t char_entity(
937 struct Blob *ob,
938 struct render *rndr,
939 char *data,
940 size_t offset,
@@ -869,12 +964,13 @@
964 blob_append(ob, data, end);
965 }
966 return end;
967 }
968
969 /*
970 ** char_langle_tag -- '<' when tags or autolinks are allowed.
971 */
972 static size_t char_langle_tag(
973 struct Blob *ob,
974 struct render *rndr,
975 char *data,
976 size_t offset,
@@ -899,12 +995,12 @@
995 }else{
996 return end;
997 }
998 }
999
1000 /*
1001 ** get_link_inline -- extract inline-style link and title from
1002 ** parenthesed data
1003 */
1004 static int get_link_inline(
1005 struct Blob *link,
1006 struct Blob *title,
@@ -954,11 +1050,11 @@
1050 link_e--;
1051 }
1052
1053 /* remove optional angle brackets around the link */
1054 if( data[link_b]=='<' ) link_b += 1;
1055 if( link_e && data[link_e-1]=='>' ) link_e -= 1;
1056
1057 /* escape backslashed character from link */
1058 blob_reset(link);
1059 i = link_b;
1060 while( i<link_e ){
@@ -975,145 +1071,353 @@
1071 /* this function always succeed */
1072 return 0;
1073 }
1074
1075
1076 /*
1077 ** get_link_ref -- extract referenced link and title from id.
1078 */
1079 static int get_link_ref(
1080 struct render *rndr,
1081 struct Blob *link,
1082 struct Blob *title,
1083 char *data,
1084 size_t size
1085 ){
1086 struct link_ref *lr;
1087 const size_t sz = blob_size(&rndr->refs);
1088
1089 /* find the link from its id (stored temporarily in link) */
1090 blob_reset(link);
1091 if( !sz || build_ref_id(link, data, size)<0 ) return -1;
1092 lr = bsearch(link,
1093 blob_buffer(&rndr->refs),
1094 sz/sizeof(struct link_ref),
1095 sizeof (struct link_ref),
1096 cmp_link_ref);
1097 if( !lr ) return -1;
1098
1099 /* fill the output buffers */
1100 blob_reset(link);
1101 blob_reset(title);
1102 blob_appendb(link, &lr->link);
1103 blob_appendb(title, &lr->title);
1104 return 0;
1105 }
1106
1107 /*
1108 ** get_footnote() -- find a footnote by label, invoked during the 2nd pass.
1109 ** If found then return a shallow copy of the corresponding footnote;
1110 ** otherwise return a shallow copy of rndr->notes.misref.
1111 ** In both cases corresponding `nUsed` field is incremented before return.
1112 */
1113 static struct footnote get_footnote(
1114 struct render *rndr,
1115 const char *data,
1116 size_t size
1117 ){
1118 struct footnote *fn = 0;
1119 struct Blob *id;
1120 if( !rndr->notes.nLbled ) goto fallback;
1121 id = new_work_buffer(rndr);
1122 if( build_ref_id(id, data, size)<0 ) goto cleanup;
1123 fn = bsearch(id, blob_buffer(&rndr->notes.all),
1124 rndr->notes.nLbled,
1125 sizeof (struct footnote),
1126 cmp_link_ref);
1127 if( !fn ) goto cleanup;
1128
1129 if( fn->nUsed == 0 ){ /* the first reference to the footnote */
1130 assert( fn->iMark == 0 );
1131 fn->iMark = ++(rndr->notes.nMarks);
1132 }
1133 assert( fn->iMark > 0 );
1134 cleanup:
1135 release_work_buffer( rndr, id );
1136 fallback:
1137 if( !fn ) fn = &rndr->notes.misref;
1138 fn->nUsed++;
1139 assert( fn->nUsed > 0 );
1140 return *fn;
1141 }
1142
1143 /*
1144 ** Counts characters in the blank prefix within at most nHalfLines.
1145 ** A sequence of spaces and tabs counts as odd halfline,
1146 ** a newline counts as even halfline.
1147 ** If nHalfLines < 0 then proceed without constraints.
1148 */
1149 static inline size_t sizeof_blank_prefix(
1150 const char *data, size_t size, int nHalfLines
1151 ){
1152 const char *p = data;
1153 const char * const end = data+size;
1154 if( nHalfLines < 0 ){
1155 while( p!=end && fossil_isspace(*p) ){
1156 p++;
1157 }
1158 }else while( nHalfLines > 0 ){
1159 while( p!=end && (*p==' ' || *p=='\t' ) ){ p++; }
1160 if( p==end || --nHalfLines == 0 ) break;
1161 if( *p=='\n' || *p=='\r' ){
1162 p++;
1163 if( p==end ) break;
1164 if( *p=='\n' && p[-1]=='\r' ){
1165 p++;
1166 }
1167 }
1168 nHalfLines--;
1169 }
1170 return p-data;
1171 }
1172
1173 /*
1174 ** Check if the data starts with a classlist token of the special form.
1175 ** If so then return the length of that token, otherwise return 0.
1176 **
1177 ** The token must start with a dot and must end with a colon;
1178 ** in between of these it must be a dot-separated list of words;
1179 ** each word may contain only alphanumeric characters and hyphens.
1180 **
1181 ** If `bBlank` is non-zero then a blank character must follow
1182 ** the token's ending colon: otherwise function returns 0
1183 ** despite the well-formed token.
1184 */
1185 static size_t is_footnote_classlist(const char * const data, size_t size,
1186 int bBlank){
1187 const char *p;
1188 const char * const end = data+size;
1189 if( data==end || *data != '.' ) return 0;
1190 for(p=data+1; p!=end; p++){
1191 if( fossil_isalnum(*p) || *p=='-' ) continue;
1192 if( p[-1]=='.' ) break;
1193 if( *p==':' ){
1194 p++;
1195 if( bBlank ){
1196 if( p==end || !fossil_isspace(*p) ) break;
1197 }
1198 return p-data;
1199 }
1200 if( *p!='.' ) break;
1201 }
1202 return 0;
1203 }
1204
1205 /*
1206 ** Adds unlabeled footnote to the rndr->notes.all.
1207 ** On success puts a shallow copy of the constructed footnote into pFN
1208 ** and returns 1, otherwise pFN is unchanged and 0 is returned.
1209 */
1210 static inline int add_inline_footnote(
1211 struct render *rndr,
1212 const char *text,
1213 size_t size,
1214 struct footnote* pFN
1215 ){
1216 struct footnote fn = FOOTNOTE_INITIALIZER, *last;
1217 const char *zUPC = 0;
1218 size_t nUPC = 0, n = sizeof_blank_prefix(text, size, 3);
1219 if( n >= size ) return 0;
1220 text += n;
1221 size -= n;
1222 nUPC = is_footnote_classlist(text, size, 1);
1223 if( nUPC ){
1224 assert( nUPC<size );
1225 zUPC = text;
1226 text += nUPC;
1227 size -= nUPC;
1228 }
1229 if( sizeof_blank_prefix(text,size,-1)==size ){
1230 if( !nUPC ) return 0; /* empty inline footnote */
1231 text = zUPC;
1232 size = nUPC; /* bare classlist is treated */
1233 nUPC = 0; /* as plain text */
1234 }
1235 fn.iMark = ++(rndr->notes.nMarks);
1236 fn.nUsed = 1;
1237 fn.index = COUNT_FOOTNOTES(&rndr->notes.all);
1238 assert( fn.iMark > 0 );
1239 blob_append(&fn.text, text, size);
1240 if(nUPC) blob_append(&fn.upc, zUPC, nUPC);
1241 blob_append(&rndr->notes.all, (char *)&fn, sizeof fn);
1242 last = (struct footnote*)( blob_buffer(&rndr->notes.all)
1243 +( blob_size(&rndr->notes.all)-sizeof fn ));
1244 assert( pFN );
1245 memcpy( pFN, last, sizeof fn );
1246 return 1;
1247 }
1248
1249 /*
1250 ** Return the byte offset of the matching closing bracket or 0 if not
1251 ** found. begin[0] must be either '[' or '('.
1252 **
1253 ** TODO: It seems that things like "\\(" are not handled correctly.
1254 ** That is historical behavior for a corner-case,
1255 ** so it's left as it is until somebody complains.
1256 */
1257 static inline size_t matching_bracket_offset(
1258 const char* begin,
1259 const char* end
1260 ){
1261 const char *i;
1262 int level;
1263 const char bra = *begin;
1264 const char ket = bra=='[' ? ']' : ')';
1265 assert( bra=='[' || bra=='(' );
1266 for(i=begin+1,level=1; i!=end; i++){
1267 if( *i=='\n' ) /* do nothing */;
1268 else if( i[-1]=='\\' ) continue;
1269 else if( *i==bra ) level++;
1270 else if( *i==ket ){
1271 if( --level<=0 ) return i-begin;
1272 }
1273 }
1274 return 0;
1275 }
1276
1277 /*
1278 ** char_footnote -- '(': parsing a standalone inline footnote.
1279 */
1280 static size_t char_footnote(
1281 struct Blob *ob,
1282 struct render *rndr,
1283 char *data,
1284 size_t offset,
1285 size_t size
1286 ){
1287 size_t end;
1288 struct footnote fn;
1289
1290 if( size<4 || data[1]!='^' ) return 0;
1291 end = matching_bracket_offset(data, data+size);
1292 if( !end ) return 0;
1293 if( !add_inline_footnote(rndr, data+2, end-2, &fn) ) return 0;
1294 if( rndr->make.footnote_ref ){
1295 rndr->make.footnote_ref(ob,0,&fn.upc,fn.iMark,1,rndr->make.opaque);
1296 }
1297 return end+1;
1298 }
1299
1300 /*
1301 ** char_link -- '[': parsing a link or an image.
1302 */
1303 static size_t char_link(
1304 struct Blob *ob,
1305 struct render *rndr,
1306 char *data,
1307 size_t offset,
1308 size_t size /* parse_inline() ensures that size > 0 */
1309 ){
1310 const int is_img = (offset && data[-1] == '!');
1311 size_t i = 1, txt_e;
1312 struct Blob *content = 0;
1313 struct Blob *link = 0;
1314 struct Blob *title = 0;
1315 struct footnote fn;
1316 int ret;
1317
1318 /* checking whether the correct renderer exists */
1319 if( (is_img && !rndr->make.image) || (!is_img && !rndr->make.link) ){
1320 return 0;
1321 }
1322
1323 /* looking for the matching closing bracket */
1324 txt_e = matching_bracket_offset(data, data+size);
1325 if( !txt_e ) return 0;
1326 i = txt_e + 1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1327 ret = 0; /* error if we don't get to the callback */
1328 fn.nUsed = 0;
1329
1330 /* free-standing footnote refernece */
1331 if(!is_img && size>3 && data[1]=='^'){
1332 fn = get_footnote(rndr, data+2, txt_e-2);
1333 }else{
1334
1335 /* skip "inter-bracket-whitespace" - any amount of whitespace or newline */
1336 /* (this is much more lax than original markdown syntax) */
1337 while( i<size && (data[i]==' ' || data[i]=='\t' || data[i]=='\n') ){ i++; }
1338
1339 /* allocate temporary buffers to store content, link and title */
1340 title = new_work_buffer(rndr);
1341 content = new_work_buffer(rndr);
1342 link = new_work_buffer(rndr);
1343
1344 if( i<size && data[i]=='(' ){
1345
1346 if( i+2<size && data[i+1]=='^' ){ /* span-bounded inline footnote */
1347
1348 const size_t k = matching_bracket_offset(data+i, data+size);
1349 if( !k ) goto char_link_cleanup;
1350 add_inline_footnote(rndr, data+(i+2), k-2, &fn);
1351 i += k+1;
1352 }else{ /* inline style link */
1353 size_t span_end = i;
1354 while( span_end<size
1355 && !(data[span_end]==')'
1356 && (span_end==i || data[span_end-1]!='\\')) ){
1357 span_end++;
1358 }
1359 if( span_end>=size
1360 || get_link_inline(link, title, data+i+1, span_end-(i+1))<0 ){
1361 goto char_link_cleanup;
1362 }
1363 i = span_end+1;
1364 }
1365 /* reference style link or span-bounded footnote reference */
1366 }else if( i<size && data[i]=='[' ){
1367 char *id_data;
1368 size_t id_size, id_end = i;
1369 int bFootnote;
1370
1371 while( id_end<size && data[id_end]!=']' ){ id_end++; }
1372 if( id_end>=size ) goto char_link_cleanup;
1373 bFootnote = data[i+1]=='^';
1374 if( i+1==id_end || (bFootnote && i+2==id_end) ){
1375 /* implicit id - use the contents */
1376 id_data = data+1;
1377 id_size = txt_e-1;
1378 }else{
1379 /* explicit id - between brackets */
1380 id_data = data+i+1;
1381 id_size = id_end-(i+1);
1382 if( bFootnote ){
1383 id_data++;
1384 id_size--;
1385 }
1386 }
1387 if( bFootnote ){
1388 fn = get_footnote(rndr, id_data, id_size);
1389 }else if( get_link_ref(rndr, link, title, id_data, id_size)<0 ){
1390 goto char_link_cleanup;
1391 }
1392 i = id_end+1;
1393 /* shortcut reference style link */
1394 }else{
1395 if( get_link_ref(rndr, link, title, data+1, txt_e-1)<0 ){
1396 goto char_link_cleanup;
1397 }
1398 /* rewinding an "inter-bracket-whitespace" */
1399 i = txt_e+1;
1400 }
1401 }
1402 /* building content: img alt is escaped, link content is parsed */
1403 if( txt_e>1 && content ){
1404 if( is_img ) blob_append(content, data+1, txt_e-1);
1405 else parse_inline(content, rndr, data+1, txt_e-1);
1406 }
1407
1408 /* calling the relevant rendering function */
1409 if( is_img ){
1410 if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ){
1411 ob->nUsed--;
1412 }
1413 ret = rndr->make.image(ob, link, title, content, rndr->make.opaque);
1414 }else if( fn.nUsed ){
1415 if( rndr->make.footnote_ref ){
1416 ret = rndr->make.footnote_ref(ob, content, &fn.upc, fn.iMark,
1417 fn.nUsed, rndr->make.opaque);
1418 }
1419 }else{
1420 ret = rndr->make.link(ob, link, title, content, rndr->make.opaque);
1421 }
1422
1423 /* cleanup */
@@ -1121,11 +1425,10 @@
1425 release_work_buffer(rndr, title);
1426 release_work_buffer(rndr, link);
1427 release_work_buffer(rndr, content);
1428 return ret ? i : 0;
1429 }
 
1430
1431
1432 /*********************************
1433 * BLOCK-LEVEL PARSING FUNCTIONS *
1434 *********************************/
@@ -1806,11 +2109,12 @@
2109 /* the end of the block has been found */
2110 if( strcmp(curtag->text,"html")==0 ){
2111 /* Omit <html> tags */
2112 enum mkd_autolink dummy;
2113 int k = tag_length(data, size, &dummy);
2114 int sz = i - (j+k);
2115 if( sz>0 ) blob_init(&work, data+k, sz);
2116 }else{
2117 blob_init(&work, data, i);
2118 }
2119 if( rndr->make.blockhtml ){
2120 rndr->make.blockhtml(ob, &work, rndr->make.opaque);
@@ -2013,11 +2317,13 @@
2317 char *data, /* input text */
2318 size_t size /* input text size */
2319 ){
2320 size_t beg, end, i;
2321 char *txt_data;
2322 int has_table;
2323 if( !size ) return;
2324 has_table = (rndr->make.table
2325 && rndr->make.table_row
2326 && rndr->make.table_cell
2327 && memchr(data, '|', size)!=0);
2328
2329 beg = 0;
@@ -2063,11 +2369,11 @@
2369 * REFERENCE PARSING *
2370 *********************/
2371
2372 /* is_ref -- returns whether a line is a reference or not */
2373 static int is_ref(
2374 const char *data, /* input text */
2375 size_t beg, /* offset of the beginning of the line */
2376 size_t end, /* offset of the end of the text */
2377 size_t *last, /* last character of the link */
2378 struct Blob *refs /* array of link references */
2379 ){
@@ -2097,10 +2403,12 @@
2403 i += beg;
2404
2405 /* id part: anything but a newline between brackets */
2406 if( data[i]!='[' ) return 0;
2407 i++;
2408 if( i>=end || data[i]=='^' ) return 0; /* see is_footnote() */
2409
2410 id_offset = i;
2411 while( i<end && data[i]!='\n' && data[i]!='\r' && data[i]!=']' ){ i++; }
2412 if( i>=end || data[i]!=']' ) return 0;
2413 id_end = i;
2414
@@ -2125,10 +2433,11 @@
2433 && data[i]!='\n'
2434 && data[i]!='\r'
2435 ){
2436 i += 1;
2437 }
2438 /* TODO: maybe require both data[i-1]=='>' && data[link_offset-1]=='<' ? */
2439 if( data[i-1]=='>' ) link_end = i-1; else link_end = i;
2440
2441 /* optional spacer: (space | tab)* (newline | '\'' | '"' | '(' ) */
2442 while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; }
2443 if( i<end
@@ -2184,34 +2493,168 @@
2493 }
2494 blob_append(refs, (char *)&lr, sizeof lr);
2495 return 1;
2496 }
2497
2498 /*********************
2499 * FOOTNOTE PARSING *
2500 *********************/
2501
2502 /* is_footnote -- check if data holds a definition of a labeled footnote.
2503 * If so then append the corresponding element to `footnotes` array */
2504 static int is_footnote(
2505 const char *data, /* input text */
2506 size_t beg, /* offset of the beginning of the line */
2507 size_t end, /* offset of the end of the text */
2508 size_t *last, /* last character of the link */
2509 struct Blob * footnotes
2510 ){
2511 size_t i, id_offset, id_end, upc_offset, upc_size;
2512 struct footnote fn = FOOTNOTE_INITIALIZER;
2513
2514 /* failfast if data is too short */
2515 if( beg+5>=end ) return 0;
2516 i = beg;
2517
2518 /* footnote definition must start at the begining of a line */
2519 if( data[i]!='[' ) return 0;
2520 i++;
2521 if( data[i]!='^' ) return 0;
2522 id_offset = ++i;
2523
2524 /* id part: anything but a newline between brackets */
2525 while( i<end && data[i]!=']' && data[i]!='\n' && data[i]!='\r' ){ i++; }
2526 if( i>=end || data[i]!=']' ) return 0;
2527 id_end = i++;
2528
2529 /* spacer: colon (space | tab)* */
2530 if( i>=end || data[i]!=':' ) return 0;
2531 i++;
2532 while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; }
2533
2534 /* passthrough truncated footnote definition */
2535 if( i>=end ) return 0;
2536
2537 if( build_ref_id(&fn.id, data+id_offset, id_end-id_offset)<0 ) return 0;
2538
2539 /* footnote's text may start on the same line after [^id]: */
2540 upc_offset = upc_size = 0;
2541 if( data[i]!='\n' && data[i]!='\r' ){
2542 size_t j;
2543 upc_size = is_footnote_classlist(data+i, end-i, 1);
2544 upc_offset = i; /* prevent further checks for a classlist */
2545 i += upc_size;
2546 j = i;
2547 while( i<end && data[i]!='\n' && data[i]!='\r' ){ i++; };
2548 if( i!=j )blob_append(&fn.text, data+j, i-j);
2549 if( i<end ){
2550 blob_append_char(&fn.text, data[i]);
2551 i++;
2552 if( i<end && data[i]=='\n' && data[i-1]=='\r' ){
2553 blob_append_char(&fn.text, data[i]);
2554 i++;
2555 }
2556 }
2557 }else{
2558 i++;
2559 if( i<end && data[i]=='\n' && data[i-1]=='\r' ) i++;
2560 }
2561 if( i<end ){
2562
2563 /* compute the indentation from the 2nd line */
2564 size_t indent = i;
2565 const char *spaces = data+i;
2566 while( indent<end && data[indent]==' ' ){ indent++; }
2567 if( indent>=end ) goto footnote_finish;
2568 indent -= i;
2569 if( indent<2 ) goto footnote_finish;
2570
2571 /* process the 2nd and subsequent lines */
2572 while( i+indent<end && memcmp(data+i,spaces,indent)==0 ){
2573 size_t j;
2574 i += indent;
2575 if( !upc_offset ){
2576 /* a classlist must be provided no later than at the 2nd line */
2577 upc_offset = i + sizeof_blank_prefix(data+i, end-i, 1);
2578 upc_size = is_footnote_classlist(data+upc_offset,
2579 end-upc_offset, 1);
2580 if( upc_size ){
2581 i = upc_offset + upc_size;
2582 }
2583 }
2584 j = i;
2585 while( i<end && data[i]!='\n' && data[i]!='\r' ){ i++; }
2586 if( i!=j ) blob_append(&fn.text, data+j, i-j);
2587 if( i>=end ) break;
2588 blob_append_char(&fn.text, data[i]);
2589 i++;
2590 if( i<end && data[i]=='\n' && data[i-1]=='\r' ){
2591 blob_append_char(&fn.text, data[i]);
2592 i++;
2593 }
2594 }
2595 }
2596 footnote_finish:
2597 if( !blob_size(&fn.text) ){
2598 blob_reset(&fn.id);
2599 return 0;
2600 }
2601 if( !blob_trim(&fn.text) ){ /* if the content is all-blank */
2602 if( upc_size ){ /* interpret UPC as plain text */
2603 blob_append(&fn.text, data+upc_offset, upc_size);
2604 upc_size = 0;
2605 }else{
2606 blob_reset(&fn.id); /* or clean up and fail */
2607 blob_reset(&fn.text);
2608 return 0;
2609 }
2610 }
2611 /* a valid note has been found */
2612 if( last ) *last = i;
2613 if( footnotes ){
2614 fn.defno = COUNT_FOOTNOTES( footnotes );
2615 if( upc_size ){
2616 assert( upc_offset && upc_offset+upc_size<end );
2617 blob_append(&fn.upc, data+upc_offset, upc_size);
2618 }
2619 blob_append(footnotes, (char *)&fn, sizeof fn);
2620 }
2621 return 1;
2622 }
2623
2624 /**********************
2625 * EXPORTED FUNCTIONS *
2626 **********************/
2627
2628 /* markdown -- parses the input buffer and renders it into the output buffer */
2629 void markdown(
2630 struct Blob *ob, /* output blob for rendered text */
2631 const struct Blob *ib, /* input blob in markdown */
2632 const struct mkd_renderer *rndrer /* renderer descriptor (callbacks) */
2633 ){
2634 struct link_ref *lr;
2635 struct footnote *fn;
2636 size_t i, beg, end = 0;
2637 struct render rndr;
2638 Blob text = BLOB_INITIALIZER; /* input after the first pass */
2639 Blob * const allNotes = &rndr.notes.all;
2640
2641 /* filling the render structure */
2642 if( !rndrer ) return;
2643 rndr.make = *rndrer;
2644 rndr.nBlobCache = 0;
2645 rndr.iDepth = 0;
2646 rndr.refs = empty_blob;
2647 rndr.notes.all = empty_blob;
2648 rndr.notes.nMarks = 0;
2649 rndr.notes.misref.id = empty_blob;
2650 rndr.notes.misref.text = empty_blob;
2651 rndr.notes.misref.upc = empty_blob;
2652 rndr.notes.misref.bRndred = 0;
2653 rndr.notes.misref.nUsed = 0;
2654 rndr.notes.misref.iMark = -1;
2655
2656 for(i=0; i<256; i++) rndr.active_char[i] = 0;
2657 if( (rndr.make.emphasis
2658 || rndr.make.double_emphasis
2659 || rndr.make.triple_emphasis)
2660 && rndr.make.emph_chars
@@ -2221,32 +2664,34 @@
2664 }
2665 }
2666 if( rndr.make.codespan ) rndr.active_char['`'] = char_codespan;
2667 if( rndr.make.linebreak ) rndr.active_char['\n'] = char_linebreak;
2668 if( rndr.make.image || rndr.make.link ) rndr.active_char['['] = char_link;
2669 if( rndr.make.footnote_ref ) rndr.active_char['('] = char_footnote;
2670 rndr.active_char['<'] = char_langle_tag;
2671 rndr.active_char['\\'] = char_escape;
2672 rndr.active_char['&'] = char_entity;
2673
2674 /* first pass: iterate over lines looking for references,
2675 * copying everything else into "text" */
2676 beg = 0;
2677 for(const size_t size = blob_size(ib); beg<size ;){
2678 const char* const data = blob_buffer(ib);
2679 if( is_ref(data, beg, size, &end, &rndr.refs) ){
2680 beg = end;
2681 }else if(is_footnote(data, beg, size, &end, &rndr.notes.all)){
2682 beg = end;
2683 }else{ /* skipping to the next line */
2684 end = beg;
2685 while( end<size && data[end]!='\n' && data[end]!='\r' ){
2686 end += 1;
2687 }
2688 /* adding the line body if present */
2689 if( end>beg ) blob_append(&text, data + beg, end - beg);
2690 while( end<size && (data[end]=='\n' || data[end]=='\r') ){
2691 /* add one \n per newline */
2692 if( data[end]=='\n' || (end+1<size && data[end+1]!='\n') ){
 
 
2693 blob_append_char(&text, '\n');
2694 }
2695 end += 1;
2696 }
2697 beg = end;
@@ -2258,14 +2703,160 @@
2703 qsort(blob_buffer(&rndr.refs),
2704 blob_size(&rndr.refs)/sizeof(struct link_ref),
2705 sizeof(struct link_ref),
2706 cmp_link_ref_sort);
2707 }
2708 rndr.notes.nLbled = COUNT_FOOTNOTES( allNotes );
2709
2710 /* sort footnotes by ID and join duplicates */
2711 if( rndr.notes.nLbled > 1 ){
2712 int nDups = 0;
2713 fn = CAST_AS_FOOTNOTES( allNotes );
2714 qsort(fn, rndr.notes.nLbled, sizeof(struct footnote), cmp_footnote_id);
2715
2716 /* concatenate footnotes with equal labels */
2717 for(i=0; i<rndr.notes.nLbled ;){
2718 struct footnote *x = fn + i;
2719 size_t j = i+1, k = blob_size(&x->text) + 64 + blob_size(&x->upc);
2720 while(j<rndr.notes.nLbled && !blob_compare(&x->id, &fn[j].id)){
2721 k += blob_size(&fn[j].text) + 10 + blob_size(&fn[j].upc);
2722 j++;
2723 nDups++;
2724 }
2725 if( i+1<j ){
2726 Blob list = empty_blob;
2727 blob_reserve(&list, k);
2728 /* must match _joined_footnote_indicator in html_footnote_item() */
2729 blob_append_literal(&list, "<ul class='fn-joined'>\n");
2730 for(k=i; k<j; k++){
2731 struct footnote *y = fn + k;
2732 blob_append_literal(&list, "<li>");
2733 if( blob_size(&y->upc) ){
2734 blob_appendb(&list, &y->upc);
2735 blob_reset(&y->upc);
2736 }
2737 blob_appendb(&list, &y->text);
2738 blob_append_literal(&list, "</li>\n");
2739
2740 /* free memory buffer */
2741 blob_reset(&y->text);
2742 if( k!=i ) blob_reset(&y->id);
2743 }
2744 blob_append_literal(&list, "</ul>\n");
2745 x->text = list;
2746 g.ftntsIssues[2]++;
2747 }
2748 i = j;
2749 }
2750 if( nDups ){ /* clean rndr.notes.all from invalidated footnotes */
2751 const int n = rndr.notes.nLbled - nDups;
2752 struct Blob filtered = empty_blob;
2753 blob_reserve(&filtered, n*sizeof(struct footnote));
2754 for(i=0; i<rndr.notes.nLbled; i++){
2755 if( blob_size(&fn[i].id) ){
2756 blob_append(&filtered, (char*)(fn+i), sizeof(struct footnote));
2757 }
2758 }
2759 blob_reset( allNotes );
2760 rndr.notes.all = filtered;
2761 rndr.notes.nLbled = n;
2762 assert( COUNT_FOOTNOTES(allNotes) == rndr.notes.nLbled );
2763 }
2764 }
2765 fn = CAST_AS_FOOTNOTES( allNotes );
2766 for(i=0; i<rndr.notes.nLbled; i++){
2767 fn[i].index = i;
2768 }
2769 assert( rndr.notes.nMarks==0 );
2770
2771 /* second pass: actual rendering */
2772 if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
2773 parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));
2774
2775 if( blob_size(allNotes) || rndr.notes.misref.nUsed ){
2776
2777 /* Footnotes must be parsed for the correct discovery of (back)links */
2778 Blob *notes = new_work_buffer( &rndr );
2779 if( blob_size(allNotes) ){
2780 Blob *tmp = new_work_buffer( &rndr );
2781 int nMarks = -1, maxDepth = 5;
2782
2783 /* inline notes may get appended to rndr.notes.all while rendering */
2784 while(1){
2785 struct footnote *aNotes;
2786 const int N = COUNT_FOOTNOTES( allNotes );
2787
2788 /* make a shallow copy of `allNotes` */
2789 blob_truncate(notes,0);
2790 blob_appendb(notes, allNotes);
2791 aNotes = CAST_AS_FOOTNOTES(notes);
2792 qsort(aNotes, N, sizeof(struct footnote), cmp_footnote_sort);
2793
2794 if( --maxDepth < 0 || nMarks == rndr.notes.nMarks ) break;
2795 nMarks = rndr.notes.nMarks;
2796
2797 for(i=0; i<N; i++){
2798 const int j = aNotes[i].index;
2799 struct footnote *x = CAST_AS_FOOTNOTES(allNotes) + j;
2800 assert( 0<=j && j<N );
2801 if( x->bRndred || !x->nUsed ) continue;
2802 assert( x->iMark > 0 );
2803 assert( blob_size(&x->text) );
2804 blob_truncate(tmp,0);
2805
2806 /* `allNotes` may be altered and extended through this call */
2807 parse_inline(tmp, &rndr, blob_buffer(&x->text), blob_size(&x->text));
2808
2809 x = CAST_AS_FOOTNOTES(allNotes) + j;
2810 blob_truncate(&x->text,0);
2811 blob_appendb(&x->text, tmp);
2812 x->bRndred = 1;
2813 }
2814 }
2815 release_work_buffer(&rndr,tmp);
2816 }
2817
2818 /* footnotes rendering */
2819 if( rndr.make.footnote_item && rndr.make.footnotes ){
2820 Blob *all_items = new_work_buffer(&rndr);
2821 int j = -1;
2822
2823 /* Assert that the in-memory layout of id, text and upc within
2824 ** footnote struct matches the expectations of html_footnote_item()
2825 ** If it doesn't then a compiler has done something very weird.
2826 */
2827 assert( &(rndr.notes.misref.id) == &(rndr.notes.misref.text) - 1 );
2828 assert( &(rndr.notes.misref.upc) == &(rndr.notes.misref.text) + 1 );
2829
2830 for(i=0; i<COUNT_FOOTNOTES(notes); i++){
2831 const struct footnote* x = CAST_AS_FOOTNOTES(notes) + i;
2832 const int xUsed = x->bRndred ? x->nUsed : 0;
2833 if( !x->iMark ) break;
2834 assert( x->nUsed );
2835 rndr.make.footnote_item(all_items, &x->text, x->iMark,
2836 xUsed, rndr.make.opaque);
2837 if( !xUsed ) g.ftntsIssues[3]++; /* an overnested footnote */
2838 j = i;
2839 }
2840 if( rndr.notes.misref.nUsed ){
2841 rndr.make.footnote_item(all_items, 0, -1,
2842 rndr.notes.misref.nUsed, rndr.make.opaque);
2843 g.ftntsIssues[0] += rndr.notes.misref.nUsed;
2844 }
2845 while( ++j < COUNT_FOOTNOTES(notes) ){
2846 const struct footnote* x = CAST_AS_FOOTNOTES(notes) + j;
2847 assert( !x->iMark );
2848 assert( !x->nUsed );
2849 assert( !x->bRndred );
2850 rndr.make.footnote_item(all_items,&x->text,0,0,rndr.make.opaque);
2851 g.ftntsIssues[1]++;
2852 }
2853 rndr.make.footnotes(ob, all_items, rndr.make.opaque);
2854 release_work_buffer(&rndr, all_items);
2855 }
2856 release_work_buffer(&rndr, notes);
2857 }
2858 if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);
2859
2860 /* clean-up */
2861 assert( rndr.iDepth==0 );
2862 blob_reset(&text);
@@ -2275,9 +2866,17 @@
2866 blob_reset(&lr[i].id);
2867 blob_reset(&lr[i].link);
2868 blob_reset(&lr[i].title);
2869 }
2870 blob_reset(&rndr.refs);
2871 fn = CAST_AS_FOOTNOTES( allNotes );
2872 end = COUNT_FOOTNOTES( allNotes );
2873 for(i=0; i<end; i++){
2874 if(blob_size(&fn[i].id)) blob_reset(&fn[i].id);
2875 if(blob_size(&fn[i].upc)) blob_reset(&fn[i].upc);
2876 blob_reset(&fn[i].text);
2877 }
2878 blob_reset(&rndr.notes.all);
2879 for(i=0; i<rndr.nBlobCache; i++){
2880 fossil_free(rndr.aBlobCache[i]);
2881 }
2882 }
2883
--- src/markdown.md
+++ src/markdown.md
@@ -152,10 +152,42 @@
152152
>
153153
~~~ pikchr
154154
oval "Start" fit; arrow; box "Hello, World!" fit; arrow; oval "Done" fit
155155
~~~
156156
157
+<a id="ftnts"></a>
158
+## Footnotes ##
159
+
160
+> Footnotes (or "endnotes") is a Fossil's extention of classical Markdown.
161
+> Fossil's syntax for footnotes is similar to links and
162
+> is distinguished by the use of character **^**
163
+> that *immediately* follows an opening bracket.
164
+
165
+> 1. **\(^** footnote's text **)**
166
+> 2. **\[** fragment of text **]\(^** a comment about that fragment **\)**
167
+> 3. **\[^**&nbsp;label&nbsp;**\]**
168
+> 4. **\[** fragment of text **\]\[^**&nbsp;label&nbsp;**\]**
169
+> 5. **\[** fragment of text **\]\[^\]**
170
+
171
+> With formats 1 and 2 ("inline footnotes") text of a footnote is provided
172
+> in the place where the corresponding numeric mark will be rendered.
173
+> With formats 3, 4, and 5 ("reference footnotes") text of a footnote
174
+> is supplied elsewhere in the document, as shown below.
175
+> Formats 2, 4 and 5 ("span-specific footnotes") mark a specific fragment
176
+> that is being commented in the footnote.
177
+> Format 5 reuses a fragment of text as a label.
178
+> Labels are case-insensitive.
179
+
180
+> ```
181
+> [^label]: Footnote definition must start on the first column.
182
+> The second line (if any) must be indented by two or more spaces.
183
+> Definition continues until indentation drops below that of the 2nd line.
184
+>```
185
+> Character **^** is not part of a label, it is part of the syntax.
186
+> Both a footnote's text and a fragment to which a footnote applies
187
+> are subject to further interpretation as Markdown sources.
188
+
157189
## Miscellaneous ##
158190
159191
> * In-line images are made using **\!\[alt-text\]\(image-URL\)**.
160192
> * Use HTML for advanced formatting such as forms.
161193
> * **\<!--** HTML-style comments **-->** are supported.
162194
--- src/markdown.md
+++ src/markdown.md
@@ -152,10 +152,42 @@
152 >
153 ~~~ pikchr
154 oval "Start" fit; arrow; box "Hello, World!" fit; arrow; oval "Done" fit
155 ~~~
156
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157 ## Miscellaneous ##
158
159 > * In-line images are made using **\!\[alt-text\]\(image-URL\)**.
160 > * Use HTML for advanced formatting such as forms.
161 > * **\<!--** HTML-style comments **-->** are supported.
162
--- src/markdown.md
+++ src/markdown.md
@@ -152,10 +152,42 @@
152 >
153 ~~~ pikchr
154 oval "Start" fit; arrow; box "Hello, World!" fit; arrow; oval "Done" fit
155 ~~~
156
157 <a id="ftnts"></a>
158 ## Footnotes ##
159
160 > Footnotes (or "endnotes") is a Fossil's extention of classical Markdown.
161 > Fossil's syntax for footnotes is similar to links and
162 > is distinguished by the use of character **^**
163 > that *immediately* follows an opening bracket.
164
165 > 1. **\(^** footnote's text **)**
166 > 2. **\[** fragment of text **]\(^** a comment about that fragment **\)**
167 > 3. **\[^**&nbsp;label&nbsp;**\]**
168 > 4. **\[** fragment of text **\]\[^**&nbsp;label&nbsp;**\]**
169 > 5. **\[** fragment of text **\]\[^\]**
170
171 > With formats 1 and 2 ("inline footnotes") text of a footnote is provided
172 > in the place where the corresponding numeric mark will be rendered.
173 > With formats 3, 4, and 5 ("reference footnotes") text of a footnote
174 > is supplied elsewhere in the document, as shown below.
175 > Formats 2, 4 and 5 ("span-specific footnotes") mark a specific fragment
176 > that is being commented in the footnote.
177 > Format 5 reuses a fragment of text as a label.
178 > Labels are case-insensitive.
179
180 > ```
181 > [^label]: Footnote definition must start on the first column.
182 > The second line (if any) must be indented by two or more spaces.
183 > Definition continues until indentation drops below that of the 2nd line.
184 >```
185 > Character **^** is not part of a label, it is part of the syntax.
186 > Both a footnote's text and a fragment to which a footnote applies
187 > are subject to further interpretation as Markdown sources.
188
189 ## Miscellaneous ##
190
191 > * In-line images are made using **\!\[alt-text\]\(image-URL\)**.
192 > * Use HTML for advanced formatting such as forms.
193 > * **\<!--** HTML-style comments **-->** are supported.
194
+355 -80
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -29,37 +29,72 @@
2929
struct Blob *output_title,
3030
struct Blob *output_body);
3131
3232
#endif /* INTERFACE */
3333
34
+/*
35
+** Markdown-internal helper for generating unique link reference IDs.
36
+** Fields provide typed interpretation of the underline memory buffer.
37
+*/
38
+typedef union bitfield64_t bitfield64_t;
39
+union bitfield64_t{
40
+ char c[8]; /* interpret as the array of signed characters */
41
+ unsigned char b[8]; /* interpret as the array of unsigned characters */
42
+};
43
+
3444
/*
3545
** An instance of the following structure is passed through the
3646
** "opaque" pointer.
3747
*/
3848
typedef struct MarkdownToHtml MarkdownToHtml;
3949
struct MarkdownToHtml {
4050
Blob *output_title; /* Store the title here */
51
+ bitfield64_t unique; /* Enables construction of unique #id elements */
52
+
53
+ #ifndef FOOTNOTES_WITHOUT_URI
54
+ Blob reqURI; /* REQUEST_URI with escaped quotes */
55
+ #endif
4156
};
4257
4358
4459
/* INTER_BLOCK -- skip a line between block level elements */
4560
#define INTER_BLOCK(ob) \
4661
do { if( blob_size(ob)>0 ) blob_append_char(ob, '\n'); } while (0)
4762
48
-/* BLOB_APPEND_LITERAL -- append a string literal to a blob */
49
-#define BLOB_APPEND_LITERAL(blob, literal) \
50
- blob_append((blob), "" literal, (sizeof literal)-1)
51
- /*
52
- * The empty string in the second argument leads to a syntax error
53
- * when the macro is not used with a string literal. Unfortunately
54
- * the error is not overly explicit.
55
- */
56
-
57
-/* BLOB_APPEND_BLOB -- append blob contents to another */
58
-#define BLOB_APPEND_BLOB(dest, src) \
59
- blob_append((dest), blob_buffer(src), blob_size(src))
60
-
63
+/*
64
+** FOOTNOTES_WITHOUT_URI macro was introduced by [2c1f8f3592ef00e0]
65
+** to enable flexibility in rendering of footnote-specific hyperlinks.
66
+** It may be defined for a particular build in order to omit
67
+** full REQUEST_URIs within footnote-specific (and page-local) hyperlinks.
68
+** This *is* used for the builds that incorporate 'base-href-fix' branch
69
+** (which in turn fixes footnotes on the preview tab of /wikiedit page).
70
+*/
71
+#ifndef FOOTNOTES_WITHOUT_URI
72
+ #define BLOB_APPEND_URI(dest,ctx) blob_appendb(dest,&((ctx)->reqURI))
73
+#else
74
+ #define BLOB_APPEND_URI(dest,ctx)
75
+#endif
76
+
77
+/* Converts an integer to a textual base26 representation
78
+** with proper null-termination.
79
+ * Return empty string if that integer is negative. */
80
+static bitfield64_t to_base26(int i, int uppercase){
81
+ bitfield64_t x;
82
+ int j;
83
+ memset( &x, 0, sizeof(x) );
84
+ if( i >= 0 ){
85
+ for(j=7; j >= 0; j--){
86
+ x.b[j] = (unsigned char)(uppercase?'A':'a') + i%26;
87
+ if( (i /= 26) == 0 ) break;
88
+ }
89
+ assert( j > 0 ); /* because 2^32 < 26^7 */
90
+ for(i=0; i<8-j; i++) x.b[i] = x.b[i+j];
91
+ for( ; i<8 ; i++) x.b[i] = 0;
92
+ }
93
+ assert( x.c[7] == 0 );
94
+ return x;
95
+}
6196
6297
/* HTML escapes
6398
**
6499
** html_escape() converts < to &lt;, > to &gt;, and & to &amp;.
65100
** html_quote() goes further and converts " into &quot; and ' in &#39;.
@@ -78,19 +113,19 @@
78113
i++;
79114
}
80115
blob_append(ob, data+beg, i-beg);
81116
while( i<size ){
82117
if( data[i]=='<' ){
83
- BLOB_APPEND_LITERAL(ob, "&lt;");
118
+ blob_append_literal(ob, "&lt;");
84119
}else if( data[i]=='>' ){
85
- BLOB_APPEND_LITERAL(ob, "&gt;");
120
+ blob_append_literal(ob, "&gt;");
86121
}else if( data[i]=='&' ){
87
- BLOB_APPEND_LITERAL(ob, "&amp;");
122
+ blob_append_literal(ob, "&amp;");
88123
}else if( data[i]=='"' ){
89
- BLOB_APPEND_LITERAL(ob, "&quot;");
124
+ blob_append_literal(ob, "&quot;");
90125
}else if( data[i]=='\'' ){
91
- BLOB_APPEND_LITERAL(ob, "&#39;");
126
+ blob_append_literal(ob, "&#39;");
92127
}else{
93128
break;
94129
}
95130
i++;
96131
}
@@ -108,15 +143,15 @@
108143
i++;
109144
}
110145
blob_append(ob, data+beg, i-beg);
111146
while( i<size ){
112147
if( data[i]=='<' ){
113
- BLOB_APPEND_LITERAL(ob, "&lt;");
148
+ blob_append_literal(ob, "&lt;");
114149
}else if( data[i]=='>' ){
115
- BLOB_APPEND_LITERAL(ob, "&gt;");
150
+ blob_append_literal(ob, "&gt;");
116151
}else if( data[i]=='&' ){
117
- BLOB_APPEND_LITERAL(ob, "&amp;");
152
+ blob_append_literal(ob, "&amp;");
118153
}else{
119154
break;
120155
}
121156
i++;
122157
}
@@ -129,17 +164,17 @@
129164
/* Size of the prolog: "<div class='markdown'>\n" */
130165
#define PROLOG_SIZE 23
131166
132167
static void html_prolog(struct Blob *ob, void *opaque){
133168
INTER_BLOCK(ob);
134
- BLOB_APPEND_LITERAL(ob, "<div class=\"markdown\">\n");
169
+ blob_append_literal(ob, "<div class=\"markdown\">\n");
135170
assert( blob_size(ob)==PROLOG_SIZE );
136171
}
137172
138173
static void html_epilog(struct Blob *ob, void *opaque){
139174
INTER_BLOCK(ob);
140
- BLOB_APPEND_LITERAL(ob, "</div>\n");
175
+ blob_append_literal(ob, "</div>\n");
141176
}
142177
143178
static void html_blockhtml(struct Blob *ob, struct Blob *text, void *opaque){
144179
char *data = blob_buffer(text);
145180
size_t size = blob_size(text);
@@ -157,25 +192,25 @@
157192
blob_append(title, data+nTag, size - nTag - 5);
158193
return;
159194
}
160195
INTER_BLOCK(ob);
161196
blob_append(ob, data, size);
162
- BLOB_APPEND_LITERAL(ob, "\n");
197
+ blob_append_literal(ob, "\n");
163198
}
164199
165200
static void html_blockcode(struct Blob *ob, struct Blob *text, void *opaque){
166201
INTER_BLOCK(ob);
167
- BLOB_APPEND_LITERAL(ob, "<pre><code>");
202
+ blob_append_literal(ob, "<pre><code>");
168203
html_escape(ob, blob_buffer(text), blob_size(text));
169
- BLOB_APPEND_LITERAL(ob, "</code></pre>\n");
204
+ blob_append_literal(ob, "</code></pre>\n");
170205
}
171206
172207
static void html_blockquote(struct Blob *ob, struct Blob *text, void *opaque){
173208
INTER_BLOCK(ob);
174
- BLOB_APPEND_LITERAL(ob, "<blockquote>\n");
175
- BLOB_APPEND_BLOB(ob, text);
176
- BLOB_APPEND_LITERAL(ob, "</blockquote>\n");
209
+ blob_append_literal(ob, "<blockquote>\n");
210
+ blob_appendb(ob, text);
211
+ blob_append_literal(ob, "</blockquote>\n");
177212
}
178213
179214
static void html_header(
180215
struct Blob *ob,
181216
struct Blob *text,
@@ -184,22 +219,22 @@
184219
){
185220
struct Blob *title = ((MarkdownToHtml*)opaque)->output_title;
186221
/* The first header at the beginning of a text is considered as
187222
* a title and not output. */
188223
if( blob_size(ob)<=PROLOG_SIZE && title!=0 && blob_size(title)==0 ){
189
- BLOB_APPEND_BLOB(title, text);
224
+ blob_appendb(title, text);
190225
return;
191226
}
192227
INTER_BLOCK(ob);
193228
blob_appendf(ob, "<h%d>", level);
194
- BLOB_APPEND_BLOB(ob, text);
229
+ blob_appendb(ob, text);
195230
blob_appendf(ob, "</h%d>", level);
196231
}
197232
198233
static void html_hrule(struct Blob *ob, void *opaque){
199234
INTER_BLOCK(ob);
200
- BLOB_APPEND_LITERAL(ob, "<hr />\n");
235
+ blob_append_literal(ob, "<hr />\n");
201236
}
202237
203238
204239
static void html_list(
205240
struct Blob *ob,
@@ -210,11 +245,11 @@
210245
char ol[] = "ol";
211246
char ul[] = "ul";
212247
char *tag = (flags & MKD_LIST_ORDERED) ? ol : ul;
213248
INTER_BLOCK(ob);
214249
blob_appendf(ob, "<%s>\n", tag);
215
- BLOB_APPEND_BLOB(ob, text);
250
+ blob_appendb(ob, text);
216251
blob_appendf(ob, "</%s>\n", tag);
217252
}
218253
219254
static void html_list_item(
220255
struct Blob *ob,
@@ -223,20 +258,20 @@
223258
void *opaque
224259
){
225260
char *text_data = blob_buffer(text);
226261
size_t text_size = blob_size(text);
227262
while( text_size>0 && text_data[text_size-1]=='\n' ) text_size--;
228
- BLOB_APPEND_LITERAL(ob, "<li>");
263
+ blob_append_literal(ob, "<li>");
229264
blob_append(ob, text_data, text_size);
230
- BLOB_APPEND_LITERAL(ob, "</li>\n");
265
+ blob_append_literal(ob, "</li>\n");
231266
}
232267
233268
static void html_paragraph(struct Blob *ob, struct Blob *text, void *opaque){
234269
INTER_BLOCK(ob);
235
- BLOB_APPEND_LITERAL(ob, "<p>");
236
- BLOB_APPEND_BLOB(ob, text);
237
- BLOB_APPEND_LITERAL(ob, "</p>\n");
270
+ blob_append_literal(ob, "<p>");
271
+ blob_appendb(ob, text);
272
+ blob_append_literal(ob, "</p>\n");
238273
}
239274
240275
241276
static void html_table(
242277
struct Blob *ob,
@@ -243,71 +278,301 @@
243278
struct Blob *head_row,
244279
struct Blob *rows,
245280
void *opaque
246281
){
247282
INTER_BLOCK(ob);
248
- BLOB_APPEND_LITERAL(ob, "<table>\n");
283
+ blob_append_literal(ob, "<table>\n");
249284
if( head_row && blob_size(head_row)>0 ){
250
- BLOB_APPEND_LITERAL(ob, "<thead>\n");
251
- BLOB_APPEND_BLOB(ob, head_row);
252
- BLOB_APPEND_LITERAL(ob, "</thead>\n<tbody>\n");
285
+ blob_append_literal(ob, "<thead>\n");
286
+ blob_appendb(ob, head_row);
287
+ blob_append_literal(ob, "</thead>\n<tbody>\n");
253288
}
254289
if( rows ){
255
- BLOB_APPEND_BLOB(ob, rows);
290
+ blob_appendb(ob, rows);
256291
}
257292
if( head_row && blob_size(head_row)>0 ){
258
- BLOB_APPEND_LITERAL(ob, "</tbody>\n");
293
+ blob_append_literal(ob, "</tbody>\n");
259294
}
260
- BLOB_APPEND_LITERAL(ob, "</table>\n");
295
+ blob_append_literal(ob, "</table>\n");
261296
}
262297
263298
static void html_table_cell(
264299
struct Blob *ob,
265300
struct Blob *text,
266301
int flags,
267302
void *opaque
268303
){
269304
if( flags & MKD_CELL_HEAD ){
270
- BLOB_APPEND_LITERAL(ob, " <th");
305
+ blob_append_literal(ob, " <th");
271306
}else{
272
- BLOB_APPEND_LITERAL(ob, " <td");
307
+ blob_append_literal(ob, " <td");
273308
}
274309
switch( flags & MKD_CELL_ALIGN_MASK ){
275310
case MKD_CELL_ALIGN_LEFT: {
276
- BLOB_APPEND_LITERAL(ob, " align=\"left\"");
311
+ blob_append_literal(ob, " align=\"left\"");
277312
break;
278313
}
279314
case MKD_CELL_ALIGN_RIGHT: {
280
- BLOB_APPEND_LITERAL(ob, " align=\"right\"");
315
+ blob_append_literal(ob, " align=\"right\"");
281316
break;
282317
}
283318
case MKD_CELL_ALIGN_CENTER: {
284
- BLOB_APPEND_LITERAL(ob, " align=\"center\"");
319
+ blob_append_literal(ob, " align=\"center\"");
285320
break;
286321
}
287322
}
288
- BLOB_APPEND_LITERAL(ob, ">");
289
- BLOB_APPEND_BLOB(ob, text);
323
+ blob_append_literal(ob, ">");
324
+ blob_appendb(ob, text);
290325
if( flags & MKD_CELL_HEAD ){
291
- BLOB_APPEND_LITERAL(ob, "</th>\n");
326
+ blob_append_literal(ob, "</th>\n");
292327
}else{
293
- BLOB_APPEND_LITERAL(ob, "</td>\n");
328
+ blob_append_literal(ob, "</td>\n");
294329
}
295330
}
296331
297332
static void html_table_row(
298333
struct Blob *ob,
299334
struct Blob *cells,
300335
int flags,
301336
void *opaque
302337
){
303
- BLOB_APPEND_LITERAL(ob, " <tr>\n");
304
- BLOB_APPEND_BLOB(ob, cells);
305
- BLOB_APPEND_LITERAL(ob, " </tr>\n");
338
+ blob_append_literal(ob, " <tr>\n");
339
+ blob_appendb(ob, cells);
340
+ blob_append_literal(ob, " </tr>\n");
341
+}
342
+
343
+/*
344
+** Render a token of user provided classes.
345
+** If bHTML is true then render HTML for (presumably) visible text,
346
+** otherwise just a space-separated list of the derived classes.
347
+*/
348
+static void append_footnote_upc(
349
+ struct Blob *ob,
350
+ const struct Blob *upc, /* token of user-provided classes */
351
+ int bHTML
352
+){
353
+ const char *z = blob_buffer(upc);
354
+ int i, n = blob_size(upc);
355
+
356
+ if( n<3 ) return;
357
+ assert( z[0]=='.' && z[n-1] == ':' );
358
+ if( bHTML ){
359
+ blob_append_literal(ob, "<span class='fn-upc'>"
360
+ "<span class='fn-upcDot'>.</span>");
361
+ }
362
+ n = 0;
363
+ do{
364
+ z++;
365
+ if( *z!='.' && *z!=':' ){
366
+ assert( fossil_isalnum(*z) || *z=='-' );
367
+ n++;
368
+ continue;
369
+ }
370
+ assert( n );
371
+ if( bHTML ) blob_append_literal(ob, "<span class='");
372
+ blob_append_literal(ob, "fn-upc-");
373
+
374
+ for(i=-n; i<0; i++){
375
+ blob_append_char(ob, fossil_tolower(z[i]) );
376
+ }
377
+ if( bHTML ){
378
+ blob_append_literal(ob, "'>");
379
+ blob_append(ob, z-n, n);
380
+ blob_append_literal(ob, "</span>");
381
+ }else{
382
+ blob_append_char(ob, ' ');
383
+ }
384
+ n = 0;
385
+ if( bHTML ){
386
+ if( *z==':' ){
387
+ blob_append_literal(ob,"<span class='fn-upcColon'>:</span>");
388
+ }else{
389
+ blob_append_literal(ob,"<span class='fn-upcDot'>.</span>");
390
+ }
391
+ }
392
+ }while( *z != ':' );
393
+ if( bHTML ) blob_append_literal(ob,"</span>\n");
394
+}
395
+
396
+static int html_footnote_ref(
397
+ struct Blob *ob, const struct Blob *span, const struct Blob *upc,
398
+ int iMark, int locus, void *opaque
399
+){
400
+ const struct MarkdownToHtml* ctx = (struct MarkdownToHtml*)opaque;
401
+ const bitfield64_t l = to_base26(locus-1,0);
402
+ char pos[32];
403
+ memset(pos,0,32);
404
+ assert( locus > 0 );
405
+ /* expect BUGs if the following yields compiler warnings */
406
+ if( iMark > 0 ){ /* a regular reference to a footnote */
407
+ sprintf(pos, "%s-%d-%s", ctx->unique.c, iMark, l.c);
408
+ if(span && blob_size(span)) {
409
+ blob_append_literal(ob,"<span class='");
410
+ append_footnote_upc(ob, upc, 0);
411
+ blob_append_literal(ob,"notescope' id='noteref");
412
+ blob_appendf(ob,"%s'>",pos);
413
+ blob_appendb(ob, span);
414
+ blob_trim(ob);
415
+ blob_append_literal(ob,"<sup class='noteref'><a href='");
416
+ BLOB_APPEND_URI(ob, ctx);
417
+ blob_appendf(ob,"#footnote%s'>%d</a></sup></span>", pos, iMark);
418
+ }else{
419
+ blob_trim(ob);
420
+ blob_append_literal(ob,"<sup class='");
421
+ append_footnote_upc(ob, upc, 0);
422
+ blob_append_literal(ob,"noteref'><a href='");
423
+ BLOB_APPEND_URI(ob, ctx);
424
+ blob_appendf(ob,"#footnote%s' id='noteref%s'>%d</a></sup>",
425
+ pos, pos, iMark);
426
+ }
427
+ }else{ /* misreference */
428
+ assert( iMark == -1 );
429
+
430
+ sprintf(pos, "%s-%s", ctx->unique.c, l.c);
431
+ if(span && blob_size(span)) {
432
+ blob_appendf(ob, "<span class='notescope' id='misref%s'>", pos);
433
+ blob_appendb(ob, span);
434
+ blob_trim(ob);
435
+ blob_append_literal(ob, "<sup class='noteref misref'><a href='");
436
+ BLOB_APPEND_URI(ob, ctx);
437
+ blob_appendf(ob, "#misreference%s'>misref</a></sup></span>", pos);
438
+ }else{
439
+ blob_trim(ob);
440
+ blob_append_literal(ob, "<sup class='noteref misref'><a href='");
441
+ BLOB_APPEND_URI(ob, ctx);
442
+ blob_appendf(ob, "#misreference%s' id='misref%s'>", pos, pos);
443
+ blob_append_literal(ob, "misref</a></sup>");
444
+ }
445
+ }
446
+ return 1;
447
+}
448
+
449
+/* Render a single item of the footnotes list.
450
+ * Each backref gets a unique id to enable dynamic styling. */
451
+static void html_footnote_item(
452
+ struct Blob *ob, const struct Blob *text, int iMark, int nUsed, void *opaque
453
+){
454
+ const struct MarkdownToHtml* ctx = (struct MarkdownToHtml*)opaque;
455
+ const char * const unique = ctx->unique.c;
456
+ assert( nUsed >= 0 );
457
+ /* expect BUGs if the following yields compiler warnings */
458
+
459
+ if( iMark < 0 ){ /* misreferences */
460
+ assert( iMark == -1 );
461
+ assert( nUsed );
462
+ blob_append_literal(ob,"<li class='fn-misreference'>"
463
+ "<sup class='fn-backrefs'>");
464
+ if( nUsed == 1 ){
465
+ blob_appendf(ob,"<a id='misreference%s-a' href='", unique);
466
+ BLOB_APPEND_URI(ob, ctx);
467
+ blob_appendf(ob,"#misref%s-a'>^</a>", unique);
468
+ }else{
469
+ int i;
470
+ blob_append_char(ob, '^');
471
+ for(i=0; i<nUsed && i<26; i++){
472
+ const int c = i + (unsigned)'a';
473
+ blob_appendf(ob," <a id='misreference%s-%c' href='", unique,c);
474
+ BLOB_APPEND_URI(ob, ctx);
475
+ blob_appendf(ob,"#misref%s-%c'>%c</a>", unique,c, c);
476
+ }
477
+ if( i < nUsed ) blob_append_literal(ob," &hellip;");
478
+ }
479
+ blob_append_literal(ob,"</sup>\n<span>Misreference</span>");
480
+ }else if( iMark > 0 ){ /* regular, joined and overnested footnotes */
481
+ char pos[24];
482
+ int bJoin = 0;
483
+ #define _joined_footnote_indicator "<ul class='fn-joined'>"
484
+ #define _jfi_sz (sizeof(_joined_footnote_indicator)-1)
485
+ /* make.footnote_item() invocations should pass args accordingly */
486
+ const struct Blob *upc = text+1;
487
+ assert( text );
488
+ /* allow blob_size(text)==0 for constructs like [...](^ [] ()) */
489
+ memset(pos,0,24);
490
+ sprintf(pos, "%s-%d", unique, iMark);
491
+ blob_appendf(ob, "<li id='footnote%s' class='", pos);
492
+ if( nUsed ){
493
+ if( blob_size(text)>=_jfi_sz &&
494
+ !memcmp(blob_buffer(text),_joined_footnote_indicator,_jfi_sz)){
495
+ bJoin = 1;
496
+ blob_append_literal(ob, "fn-joined ");
497
+ }
498
+ append_footnote_upc(ob, upc, 0);
499
+ }else{
500
+ blob_append_literal(ob, "fn-toodeep ");
501
+ }
502
+ if( nUsed <= 1 ){
503
+ blob_append_literal(ob, "fn-monoref'><sup class='fn-backrefs'>");
504
+ blob_appendf(ob,"<a id='footnote%s-a' href='", pos);
505
+ BLOB_APPEND_URI(ob, ctx);
506
+ blob_appendf(ob,"#noteref%s-a'>^</a>", pos);
507
+ }else{
508
+ int i;
509
+ blob_append_literal(ob, "fn-polyref'><sup class='fn-backrefs'>^");
510
+ for(i=0; i<nUsed && i<26; i++){
511
+ const int c = i + (unsigned)'a';
512
+ blob_appendf(ob," <a id='footnote%s-%c' href='", pos,c);
513
+ BLOB_APPEND_URI(ob, ctx);
514
+ blob_appendf(ob,"#noteref%s-%c'>%c</a>", pos,c, c);
515
+ }
516
+ /* It's unlikely that so many backrefs will be usefull */
517
+ /* but maybe for some machine generated documents... */
518
+ for(; i<nUsed && i<676; i++){
519
+ const bitfield64_t l = to_base26(i,0);
520
+ blob_appendf(ob," <a id='footnote%s-%s' href='", pos, l.c);
521
+ BLOB_APPEND_URI(ob, ctx);
522
+ blob_appendf(ob,"#noteref%s-%s'>%s</a>", pos,l.c, l.c);
523
+ }
524
+ if( i < nUsed ) blob_append_literal(ob," &hellip;");
525
+ }
526
+ blob_append_literal(ob,"</sup>\n");
527
+ if( bJoin ){
528
+ blob_append_literal(ob,"<sup class='fn-joined'></sup><ul>");
529
+ blob_append(ob,blob_buffer(text)+_jfi_sz,blob_size(text)-_jfi_sz);
530
+ }else if( nUsed ){
531
+ append_footnote_upc(ob, upc, 1);
532
+ blob_appendb(ob, text);
533
+ }else{
534
+ blob_append_literal(ob,"<i></i>\n"
535
+ "<pre><code class='language-markdown'>");
536
+ if( blob_size(upc) ){
537
+ blob_appendb(ob, upc);
538
+ }
539
+ html_escape(ob, blob_buffer(text), blob_size(text));
540
+ blob_append_literal(ob,"</code></pre>");
541
+ }
542
+ #undef _joined_footnote_indicator
543
+ #undef _jfi_sz
544
+ }else{ /* a footnote was defined but wasn't referenced */
545
+ /* make.footnote_item() invocations should pass args accordingly */
546
+ const struct Blob *id = text-1, *upc = text+1;
547
+ assert( !nUsed );
548
+ assert( text );
549
+ assert( blob_size(text) );
550
+ assert( blob_size(id) );
551
+ blob_append_literal(ob,"<li class='fn-unreferenced'>\n[^&nbsp;<code>");
552
+ html_escape(ob, blob_buffer(id), blob_size(id));
553
+ blob_append_literal(ob, "</code>&nbsp;]<i></i>\n"
554
+ "<pre><code class='language-markdown'>");
555
+ if( blob_size(upc) ){
556
+ blob_appendb(ob, upc);
557
+ }
558
+ html_escape(ob, blob_buffer(text), blob_size(text));
559
+ blob_append_literal(ob,"</code></pre>");
560
+ }
561
+ blob_append_literal(ob, "\n</li>\n");
306562
}
307563
308
-
564
+static void html_footnotes(
565
+ struct Blob *ob, const struct Blob *items, void *opaque
566
+){
567
+ if( items && blob_size(items) ){
568
+ blob_append_literal(ob,
569
+ "\n<hr class='footnotes-separator'/>\n<ol class='footnotes'>\n");
570
+ blob_appendb(ob, items);
571
+ blob_append_literal(ob, "</ol>\n");
572
+ }
573
+}
309574
310575
/* HTML span tags */
311576
312577
static int html_raw_html_tag(struct Blob *ob, struct Blob *text, void *opaque){
313578
blob_append(ob, blob_buffer(text), blob_size(text));
@@ -319,21 +584,21 @@
319584
struct Blob *link,
320585
enum mkd_autolink type,
321586
void *opaque
322587
){
323588
if( !link || blob_size(link)<=0 ) return 0;
324
- BLOB_APPEND_LITERAL(ob, "<a href=\"");
325
- if( type==MKDA_IMPLICIT_EMAIL ) BLOB_APPEND_LITERAL(ob, "mailto:");
589
+ blob_append_literal(ob, "<a href=\"");
590
+ if( type==MKDA_IMPLICIT_EMAIL ) blob_append_literal(ob, "mailto:");
326591
html_quote(ob, blob_buffer(link), blob_size(link));
327
- BLOB_APPEND_LITERAL(ob, "\">");
592
+ blob_append_literal(ob, "\">");
328593
if( type==MKDA_EXPLICIT_EMAIL && blob_size(link)>7 ){
329594
/* remove "mailto:" from displayed text */
330595
html_escape(ob, blob_buffer(link)+7, blob_size(link)-7);
331596
}else{
332597
html_escape(ob, blob_buffer(link), blob_size(link));
333598
}
334
- BLOB_APPEND_LITERAL(ob, "</a>");
599
+ blob_append_literal(ob, "</a>");
335600
return 1;
336601
}
337602
338603
/*
339604
** The nSrc bytes at zSrc[] are Pikchr input text (allegedly). Process that
@@ -422,13 +687,13 @@
422687
){
423688
if( text==0 ){
424689
/* no-op */
425690
}else if( nSep<=2 ){
426691
/* One or two graves: an in-line code span */
427
- BLOB_APPEND_LITERAL(ob, "<code>");
692
+ blob_append_literal(ob, "<code>");
428693
html_escape(ob, blob_buffer(text), blob_size(text));
429
- BLOB_APPEND_LITERAL(ob, "</code>");
694
+ blob_append_literal(ob, "</code>");
430695
}else{
431696
/* Three or more graves: a fenced code block */
432697
int n = blob_size(text);
433698
const char *z = blob_buffer(text);
434699
int i;
@@ -460,25 +725,25 @@
460725
struct Blob *ob,
461726
struct Blob *text,
462727
char c,
463728
void *opaque
464729
){
465
- BLOB_APPEND_LITERAL(ob, "<strong>");
466
- BLOB_APPEND_BLOB(ob, text);
467
- BLOB_APPEND_LITERAL(ob, "</strong>");
730
+ blob_append_literal(ob, "<strong>");
731
+ blob_appendb(ob, text);
732
+ blob_append_literal(ob, "</strong>");
468733
return 1;
469734
}
470735
471736
static int html_emphasis(
472737
struct Blob *ob,
473738
struct Blob *text,
474739
char c,
475740
void *opaque
476741
){
477
- BLOB_APPEND_LITERAL(ob, "<em>");
478
- BLOB_APPEND_BLOB(ob, text);
479
- BLOB_APPEND_LITERAL(ob, "</em>");
742
+ blob_append_literal(ob, "<em>");
743
+ blob_appendb(ob, text);
744
+ blob_append_literal(ob, "</em>");
480745
return 1;
481746
}
482747
483748
static int html_image(
484749
struct Blob *ob,
@@ -485,24 +750,24 @@
485750
struct Blob *link,
486751
struct Blob *title,
487752
struct Blob *alt,
488753
void *opaque
489754
){
490
- BLOB_APPEND_LITERAL(ob, "<img src=\"");
755
+ blob_append_literal(ob, "<img src=\"");
491756
html_quote(ob, blob_buffer(link), blob_size(link));
492
- BLOB_APPEND_LITERAL(ob, "\" alt=\"");
757
+ blob_append_literal(ob, "\" alt=\"");
493758
html_quote(ob, blob_buffer(alt), blob_size(alt));
494759
if( title && blob_size(title)>0 ){
495
- BLOB_APPEND_LITERAL(ob, "\" title=\"");
760
+ blob_append_literal(ob, "\" title=\"");
496761
html_quote(ob, blob_buffer(title), blob_size(title));
497762
}
498
- BLOB_APPEND_LITERAL(ob, "\" />");
763
+ blob_append_literal(ob, "\" />");
499764
return 1;
500765
}
501766
502767
static int html_linebreak(struct Blob *ob, void *opaque){
503
- BLOB_APPEND_LITERAL(ob, "<br />\n");
768
+ blob_append_literal(ob, "<br />\n");
504769
return 1;
505770
}
506771
507772
static int html_link(
508773
struct Blob *ob,
@@ -523,13 +788,13 @@
523788
WIKI_MARKDOWNLINKS
524789
;
525790
wiki_resolve_hyperlink(ob, flags, zLink, zClose, sizeof(zClose), 0, zTitle);
526791
}
527792
if( blob_size(content)==0 ){
528
- if( link ) BLOB_APPEND_BLOB(ob, link);
793
+ if( link ) blob_appendb(ob, link);
529794
}else{
530
- BLOB_APPEND_BLOB(ob, content);
795
+ blob_appendb(ob, content);
531796
}
532797
blob_append(ob, zClose, -1);
533798
return 1;
534799
}
535800
@@ -537,13 +802,13 @@
537802
struct Blob *ob,
538803
struct Blob *text,
539804
char c,
540805
void *opaque
541806
){
542
- BLOB_APPEND_LITERAL(ob, "<strong><em>");
543
- BLOB_APPEND_BLOB(ob, text);
544
- BLOB_APPEND_LITERAL(ob, "</em></strong>");
807
+ blob_append_literal(ob, "<strong><em>");
808
+ blob_appendb(ob, text);
809
+ blob_append_literal(ob, "</em></strong>");
545810
return 1;
546811
}
547812
548813
549814
static void html_normal_text(struct Blob *ob, struct Blob *text, void *opaque){
@@ -563,10 +828,11 @@
563828
){
564829
struct mkd_renderer html_renderer = {
565830
/* prolog and epilog */
566831
html_prolog,
567832
html_epilog,
833
+ html_footnotes,
568834
569835
/* block level elements */
570836
html_blockcode,
571837
html_blockquote,
572838
html_blockhtml,
@@ -576,10 +842,11 @@
576842
html_list_item,
577843
html_paragraph,
578844
html_table,
579845
html_table_cell,
580846
html_table_row,
847
+ html_footnote_item,
581848
582849
/* span level elements */
583850
html_autolink,
584851
html_codespan,
585852
html_double_emphasis,
@@ -587,22 +854,30 @@
587854
html_image,
588855
html_linebreak,
589856
html_link,
590857
html_raw_html_tag,
591858
html_triple_emphasis,
859
+ html_footnote_ref,
592860
593861
/* low level elements */
594862
0, /* entity */
595863
html_normal_text,
596864
597865
/* misc. parameters */
598866
"*_", /* emph_chars */
599867
0 /* opaque */
600868
};
869
+ static int invocation = -1; /* no marker for the first document */
870
+ static const char* zRU = 0; /* REQUEST_URI with escaped quotes */
601871
MarkdownToHtml context;
602872
memset(&context, 0, sizeof(context));
603873
context.output_title = output_title;
874
+ context.unique = to_base26(invocation++,1);
875
+ if( !zRU ) zRU = escape_quotes(PD("REQUEST_URI",""));
876
+ #ifndef FOOTNOTES_WITHOUT_URI
877
+ blob_set( &context.reqURI, zRU );
878
+ #endif
604879
html_renderer.opaque = &context;
605880
if( output_title ) blob_reset(output_title);
606881
blob_reset(output_body);
607882
markdown(output_body, input_markdown, &html_renderer);
608883
}
609884
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -29,37 +29,72 @@
29 struct Blob *output_title,
30 struct Blob *output_body);
31
32 #endif /* INTERFACE */
33
 
 
 
 
 
 
 
 
 
 
34 /*
35 ** An instance of the following structure is passed through the
36 ** "opaque" pointer.
37 */
38 typedef struct MarkdownToHtml MarkdownToHtml;
39 struct MarkdownToHtml {
40 Blob *output_title; /* Store the title here */
 
 
 
 
 
41 };
42
43
44 /* INTER_BLOCK -- skip a line between block level elements */
45 #define INTER_BLOCK(ob) \
46 do { if( blob_size(ob)>0 ) blob_append_char(ob, '\n'); } while (0)
47
48 /* BLOB_APPEND_LITERAL -- append a string literal to a blob */
49 #define BLOB_APPEND_LITERAL(blob, literal) \
50 blob_append((blob), "" literal, (sizeof literal)-1)
51 /*
52 * The empty string in the second argument leads to a syntax error
53 * when the macro is not used with a string literal. Unfortunately
54 * the error is not overly explicit.
55 */
56
57 /* BLOB_APPEND_BLOB -- append blob contents to another */
58 #define BLOB_APPEND_BLOB(dest, src) \
59 blob_append((dest), blob_buffer(src), blob_size(src))
60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
62 /* HTML escapes
63 **
64 ** html_escape() converts < to &lt;, > to &gt;, and & to &amp;.
65 ** html_quote() goes further and converts " into &quot; and ' in &#39;.
@@ -78,19 +113,19 @@
78 i++;
79 }
80 blob_append(ob, data+beg, i-beg);
81 while( i<size ){
82 if( data[i]=='<' ){
83 BLOB_APPEND_LITERAL(ob, "&lt;");
84 }else if( data[i]=='>' ){
85 BLOB_APPEND_LITERAL(ob, "&gt;");
86 }else if( data[i]=='&' ){
87 BLOB_APPEND_LITERAL(ob, "&amp;");
88 }else if( data[i]=='"' ){
89 BLOB_APPEND_LITERAL(ob, "&quot;");
90 }else if( data[i]=='\'' ){
91 BLOB_APPEND_LITERAL(ob, "&#39;");
92 }else{
93 break;
94 }
95 i++;
96 }
@@ -108,15 +143,15 @@
108 i++;
109 }
110 blob_append(ob, data+beg, i-beg);
111 while( i<size ){
112 if( data[i]=='<' ){
113 BLOB_APPEND_LITERAL(ob, "&lt;");
114 }else if( data[i]=='>' ){
115 BLOB_APPEND_LITERAL(ob, "&gt;");
116 }else if( data[i]=='&' ){
117 BLOB_APPEND_LITERAL(ob, "&amp;");
118 }else{
119 break;
120 }
121 i++;
122 }
@@ -129,17 +164,17 @@
129 /* Size of the prolog: "<div class='markdown'>\n" */
130 #define PROLOG_SIZE 23
131
132 static void html_prolog(struct Blob *ob, void *opaque){
133 INTER_BLOCK(ob);
134 BLOB_APPEND_LITERAL(ob, "<div class=\"markdown\">\n");
135 assert( blob_size(ob)==PROLOG_SIZE );
136 }
137
138 static void html_epilog(struct Blob *ob, void *opaque){
139 INTER_BLOCK(ob);
140 BLOB_APPEND_LITERAL(ob, "</div>\n");
141 }
142
143 static void html_blockhtml(struct Blob *ob, struct Blob *text, void *opaque){
144 char *data = blob_buffer(text);
145 size_t size = blob_size(text);
@@ -157,25 +192,25 @@
157 blob_append(title, data+nTag, size - nTag - 5);
158 return;
159 }
160 INTER_BLOCK(ob);
161 blob_append(ob, data, size);
162 BLOB_APPEND_LITERAL(ob, "\n");
163 }
164
165 static void html_blockcode(struct Blob *ob, struct Blob *text, void *opaque){
166 INTER_BLOCK(ob);
167 BLOB_APPEND_LITERAL(ob, "<pre><code>");
168 html_escape(ob, blob_buffer(text), blob_size(text));
169 BLOB_APPEND_LITERAL(ob, "</code></pre>\n");
170 }
171
172 static void html_blockquote(struct Blob *ob, struct Blob *text, void *opaque){
173 INTER_BLOCK(ob);
174 BLOB_APPEND_LITERAL(ob, "<blockquote>\n");
175 BLOB_APPEND_BLOB(ob, text);
176 BLOB_APPEND_LITERAL(ob, "</blockquote>\n");
177 }
178
179 static void html_header(
180 struct Blob *ob,
181 struct Blob *text,
@@ -184,22 +219,22 @@
184 ){
185 struct Blob *title = ((MarkdownToHtml*)opaque)->output_title;
186 /* The first header at the beginning of a text is considered as
187 * a title and not output. */
188 if( blob_size(ob)<=PROLOG_SIZE && title!=0 && blob_size(title)==0 ){
189 BLOB_APPEND_BLOB(title, text);
190 return;
191 }
192 INTER_BLOCK(ob);
193 blob_appendf(ob, "<h%d>", level);
194 BLOB_APPEND_BLOB(ob, text);
195 blob_appendf(ob, "</h%d>", level);
196 }
197
198 static void html_hrule(struct Blob *ob, void *opaque){
199 INTER_BLOCK(ob);
200 BLOB_APPEND_LITERAL(ob, "<hr />\n");
201 }
202
203
204 static void html_list(
205 struct Blob *ob,
@@ -210,11 +245,11 @@
210 char ol[] = "ol";
211 char ul[] = "ul";
212 char *tag = (flags & MKD_LIST_ORDERED) ? ol : ul;
213 INTER_BLOCK(ob);
214 blob_appendf(ob, "<%s>\n", tag);
215 BLOB_APPEND_BLOB(ob, text);
216 blob_appendf(ob, "</%s>\n", tag);
217 }
218
219 static void html_list_item(
220 struct Blob *ob,
@@ -223,20 +258,20 @@
223 void *opaque
224 ){
225 char *text_data = blob_buffer(text);
226 size_t text_size = blob_size(text);
227 while( text_size>0 && text_data[text_size-1]=='\n' ) text_size--;
228 BLOB_APPEND_LITERAL(ob, "<li>");
229 blob_append(ob, text_data, text_size);
230 BLOB_APPEND_LITERAL(ob, "</li>\n");
231 }
232
233 static void html_paragraph(struct Blob *ob, struct Blob *text, void *opaque){
234 INTER_BLOCK(ob);
235 BLOB_APPEND_LITERAL(ob, "<p>");
236 BLOB_APPEND_BLOB(ob, text);
237 BLOB_APPEND_LITERAL(ob, "</p>\n");
238 }
239
240
241 static void html_table(
242 struct Blob *ob,
@@ -243,71 +278,301 @@
243 struct Blob *head_row,
244 struct Blob *rows,
245 void *opaque
246 ){
247 INTER_BLOCK(ob);
248 BLOB_APPEND_LITERAL(ob, "<table>\n");
249 if( head_row && blob_size(head_row)>0 ){
250 BLOB_APPEND_LITERAL(ob, "<thead>\n");
251 BLOB_APPEND_BLOB(ob, head_row);
252 BLOB_APPEND_LITERAL(ob, "</thead>\n<tbody>\n");
253 }
254 if( rows ){
255 BLOB_APPEND_BLOB(ob, rows);
256 }
257 if( head_row && blob_size(head_row)>0 ){
258 BLOB_APPEND_LITERAL(ob, "</tbody>\n");
259 }
260 BLOB_APPEND_LITERAL(ob, "</table>\n");
261 }
262
263 static void html_table_cell(
264 struct Blob *ob,
265 struct Blob *text,
266 int flags,
267 void *opaque
268 ){
269 if( flags & MKD_CELL_HEAD ){
270 BLOB_APPEND_LITERAL(ob, " <th");
271 }else{
272 BLOB_APPEND_LITERAL(ob, " <td");
273 }
274 switch( flags & MKD_CELL_ALIGN_MASK ){
275 case MKD_CELL_ALIGN_LEFT: {
276 BLOB_APPEND_LITERAL(ob, " align=\"left\"");
277 break;
278 }
279 case MKD_CELL_ALIGN_RIGHT: {
280 BLOB_APPEND_LITERAL(ob, " align=\"right\"");
281 break;
282 }
283 case MKD_CELL_ALIGN_CENTER: {
284 BLOB_APPEND_LITERAL(ob, " align=\"center\"");
285 break;
286 }
287 }
288 BLOB_APPEND_LITERAL(ob, ">");
289 BLOB_APPEND_BLOB(ob, text);
290 if( flags & MKD_CELL_HEAD ){
291 BLOB_APPEND_LITERAL(ob, "</th>\n");
292 }else{
293 BLOB_APPEND_LITERAL(ob, "</td>\n");
294 }
295 }
296
297 static void html_table_row(
298 struct Blob *ob,
299 struct Blob *cells,
300 int flags,
301 void *opaque
302 ){
303 BLOB_APPEND_LITERAL(ob, " <tr>\n");
304 BLOB_APPEND_BLOB(ob, cells);
305 BLOB_APPEND_LITERAL(ob, " </tr>\n");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306 }
307
308
 
 
 
 
 
 
 
 
 
309
310 /* HTML span tags */
311
312 static int html_raw_html_tag(struct Blob *ob, struct Blob *text, void *opaque){
313 blob_append(ob, blob_buffer(text), blob_size(text));
@@ -319,21 +584,21 @@
319 struct Blob *link,
320 enum mkd_autolink type,
321 void *opaque
322 ){
323 if( !link || blob_size(link)<=0 ) return 0;
324 BLOB_APPEND_LITERAL(ob, "<a href=\"");
325 if( type==MKDA_IMPLICIT_EMAIL ) BLOB_APPEND_LITERAL(ob, "mailto:");
326 html_quote(ob, blob_buffer(link), blob_size(link));
327 BLOB_APPEND_LITERAL(ob, "\">");
328 if( type==MKDA_EXPLICIT_EMAIL && blob_size(link)>7 ){
329 /* remove "mailto:" from displayed text */
330 html_escape(ob, blob_buffer(link)+7, blob_size(link)-7);
331 }else{
332 html_escape(ob, blob_buffer(link), blob_size(link));
333 }
334 BLOB_APPEND_LITERAL(ob, "</a>");
335 return 1;
336 }
337
338 /*
339 ** The nSrc bytes at zSrc[] are Pikchr input text (allegedly). Process that
@@ -422,13 +687,13 @@
422 ){
423 if( text==0 ){
424 /* no-op */
425 }else if( nSep<=2 ){
426 /* One or two graves: an in-line code span */
427 BLOB_APPEND_LITERAL(ob, "<code>");
428 html_escape(ob, blob_buffer(text), blob_size(text));
429 BLOB_APPEND_LITERAL(ob, "</code>");
430 }else{
431 /* Three or more graves: a fenced code block */
432 int n = blob_size(text);
433 const char *z = blob_buffer(text);
434 int i;
@@ -460,25 +725,25 @@
460 struct Blob *ob,
461 struct Blob *text,
462 char c,
463 void *opaque
464 ){
465 BLOB_APPEND_LITERAL(ob, "<strong>");
466 BLOB_APPEND_BLOB(ob, text);
467 BLOB_APPEND_LITERAL(ob, "</strong>");
468 return 1;
469 }
470
471 static int html_emphasis(
472 struct Blob *ob,
473 struct Blob *text,
474 char c,
475 void *opaque
476 ){
477 BLOB_APPEND_LITERAL(ob, "<em>");
478 BLOB_APPEND_BLOB(ob, text);
479 BLOB_APPEND_LITERAL(ob, "</em>");
480 return 1;
481 }
482
483 static int html_image(
484 struct Blob *ob,
@@ -485,24 +750,24 @@
485 struct Blob *link,
486 struct Blob *title,
487 struct Blob *alt,
488 void *opaque
489 ){
490 BLOB_APPEND_LITERAL(ob, "<img src=\"");
491 html_quote(ob, blob_buffer(link), blob_size(link));
492 BLOB_APPEND_LITERAL(ob, "\" alt=\"");
493 html_quote(ob, blob_buffer(alt), blob_size(alt));
494 if( title && blob_size(title)>0 ){
495 BLOB_APPEND_LITERAL(ob, "\" title=\"");
496 html_quote(ob, blob_buffer(title), blob_size(title));
497 }
498 BLOB_APPEND_LITERAL(ob, "\" />");
499 return 1;
500 }
501
502 static int html_linebreak(struct Blob *ob, void *opaque){
503 BLOB_APPEND_LITERAL(ob, "<br />\n");
504 return 1;
505 }
506
507 static int html_link(
508 struct Blob *ob,
@@ -523,13 +788,13 @@
523 WIKI_MARKDOWNLINKS
524 ;
525 wiki_resolve_hyperlink(ob, flags, zLink, zClose, sizeof(zClose), 0, zTitle);
526 }
527 if( blob_size(content)==0 ){
528 if( link ) BLOB_APPEND_BLOB(ob, link);
529 }else{
530 BLOB_APPEND_BLOB(ob, content);
531 }
532 blob_append(ob, zClose, -1);
533 return 1;
534 }
535
@@ -537,13 +802,13 @@
537 struct Blob *ob,
538 struct Blob *text,
539 char c,
540 void *opaque
541 ){
542 BLOB_APPEND_LITERAL(ob, "<strong><em>");
543 BLOB_APPEND_BLOB(ob, text);
544 BLOB_APPEND_LITERAL(ob, "</em></strong>");
545 return 1;
546 }
547
548
549 static void html_normal_text(struct Blob *ob, struct Blob *text, void *opaque){
@@ -563,10 +828,11 @@
563 ){
564 struct mkd_renderer html_renderer = {
565 /* prolog and epilog */
566 html_prolog,
567 html_epilog,
 
568
569 /* block level elements */
570 html_blockcode,
571 html_blockquote,
572 html_blockhtml,
@@ -576,10 +842,11 @@
576 html_list_item,
577 html_paragraph,
578 html_table,
579 html_table_cell,
580 html_table_row,
 
581
582 /* span level elements */
583 html_autolink,
584 html_codespan,
585 html_double_emphasis,
@@ -587,22 +854,30 @@
587 html_image,
588 html_linebreak,
589 html_link,
590 html_raw_html_tag,
591 html_triple_emphasis,
 
592
593 /* low level elements */
594 0, /* entity */
595 html_normal_text,
596
597 /* misc. parameters */
598 "*_", /* emph_chars */
599 0 /* opaque */
600 };
 
 
601 MarkdownToHtml context;
602 memset(&context, 0, sizeof(context));
603 context.output_title = output_title;
 
 
 
 
 
604 html_renderer.opaque = &context;
605 if( output_title ) blob_reset(output_title);
606 blob_reset(output_body);
607 markdown(output_body, input_markdown, &html_renderer);
608 }
609
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -29,37 +29,72 @@
29 struct Blob *output_title,
30 struct Blob *output_body);
31
32 #endif /* INTERFACE */
33
34 /*
35 ** Markdown-internal helper for generating unique link reference IDs.
36 ** Fields provide typed interpretation of the underline memory buffer.
37 */
38 typedef union bitfield64_t bitfield64_t;
39 union bitfield64_t{
40 char c[8]; /* interpret as the array of signed characters */
41 unsigned char b[8]; /* interpret as the array of unsigned characters */
42 };
43
44 /*
45 ** An instance of the following structure is passed through the
46 ** "opaque" pointer.
47 */
48 typedef struct MarkdownToHtml MarkdownToHtml;
49 struct MarkdownToHtml {
50 Blob *output_title; /* Store the title here */
51 bitfield64_t unique; /* Enables construction of unique #id elements */
52
53 #ifndef FOOTNOTES_WITHOUT_URI
54 Blob reqURI; /* REQUEST_URI with escaped quotes */
55 #endif
56 };
57
58
59 /* INTER_BLOCK -- skip a line between block level elements */
60 #define INTER_BLOCK(ob) \
61 do { if( blob_size(ob)>0 ) blob_append_char(ob, '\n'); } while (0)
62
63 /*
64 ** FOOTNOTES_WITHOUT_URI macro was introduced by [2c1f8f3592ef00e0]
65 ** to enable flexibility in rendering of footnote-specific hyperlinks.
66 ** It may be defined for a particular build in order to omit
67 ** full REQUEST_URIs within footnote-specific (and page-local) hyperlinks.
68 ** This *is* used for the builds that incorporate 'base-href-fix' branch
69 ** (which in turn fixes footnotes on the preview tab of /wikiedit page).
70 */
71 #ifndef FOOTNOTES_WITHOUT_URI
72 #define BLOB_APPEND_URI(dest,ctx) blob_appendb(dest,&((ctx)->reqURI))
73 #else
74 #define BLOB_APPEND_URI(dest,ctx)
75 #endif
76
77 /* Converts an integer to a textual base26 representation
78 ** with proper null-termination.
79 * Return empty string if that integer is negative. */
80 static bitfield64_t to_base26(int i, int uppercase){
81 bitfield64_t x;
82 int j;
83 memset( &x, 0, sizeof(x) );
84 if( i >= 0 ){
85 for(j=7; j >= 0; j--){
86 x.b[j] = (unsigned char)(uppercase?'A':'a') + i%26;
87 if( (i /= 26) == 0 ) break;
88 }
89 assert( j > 0 ); /* because 2^32 < 26^7 */
90 for(i=0; i<8-j; i++) x.b[i] = x.b[i+j];
91 for( ; i<8 ; i++) x.b[i] = 0;
92 }
93 assert( x.c[7] == 0 );
94 return x;
95 }
96
97 /* HTML escapes
98 **
99 ** html_escape() converts < to &lt;, > to &gt;, and & to &amp;.
100 ** html_quote() goes further and converts " into &quot; and ' in &#39;.
@@ -78,19 +113,19 @@
113 i++;
114 }
115 blob_append(ob, data+beg, i-beg);
116 while( i<size ){
117 if( data[i]=='<' ){
118 blob_append_literal(ob, "&lt;");
119 }else if( data[i]=='>' ){
120 blob_append_literal(ob, "&gt;");
121 }else if( data[i]=='&' ){
122 blob_append_literal(ob, "&amp;");
123 }else if( data[i]=='"' ){
124 blob_append_literal(ob, "&quot;");
125 }else if( data[i]=='\'' ){
126 blob_append_literal(ob, "&#39;");
127 }else{
128 break;
129 }
130 i++;
131 }
@@ -108,15 +143,15 @@
143 i++;
144 }
145 blob_append(ob, data+beg, i-beg);
146 while( i<size ){
147 if( data[i]=='<' ){
148 blob_append_literal(ob, "&lt;");
149 }else if( data[i]=='>' ){
150 blob_append_literal(ob, "&gt;");
151 }else if( data[i]=='&' ){
152 blob_append_literal(ob, "&amp;");
153 }else{
154 break;
155 }
156 i++;
157 }
@@ -129,17 +164,17 @@
164 /* Size of the prolog: "<div class='markdown'>\n" */
165 #define PROLOG_SIZE 23
166
167 static void html_prolog(struct Blob *ob, void *opaque){
168 INTER_BLOCK(ob);
169 blob_append_literal(ob, "<div class=\"markdown\">\n");
170 assert( blob_size(ob)==PROLOG_SIZE );
171 }
172
173 static void html_epilog(struct Blob *ob, void *opaque){
174 INTER_BLOCK(ob);
175 blob_append_literal(ob, "</div>\n");
176 }
177
178 static void html_blockhtml(struct Blob *ob, struct Blob *text, void *opaque){
179 char *data = blob_buffer(text);
180 size_t size = blob_size(text);
@@ -157,25 +192,25 @@
192 blob_append(title, data+nTag, size - nTag - 5);
193 return;
194 }
195 INTER_BLOCK(ob);
196 blob_append(ob, data, size);
197 blob_append_literal(ob, "\n");
198 }
199
200 static void html_blockcode(struct Blob *ob, struct Blob *text, void *opaque){
201 INTER_BLOCK(ob);
202 blob_append_literal(ob, "<pre><code>");
203 html_escape(ob, blob_buffer(text), blob_size(text));
204 blob_append_literal(ob, "</code></pre>\n");
205 }
206
207 static void html_blockquote(struct Blob *ob, struct Blob *text, void *opaque){
208 INTER_BLOCK(ob);
209 blob_append_literal(ob, "<blockquote>\n");
210 blob_appendb(ob, text);
211 blob_append_literal(ob, "</blockquote>\n");
212 }
213
214 static void html_header(
215 struct Blob *ob,
216 struct Blob *text,
@@ -184,22 +219,22 @@
219 ){
220 struct Blob *title = ((MarkdownToHtml*)opaque)->output_title;
221 /* The first header at the beginning of a text is considered as
222 * a title and not output. */
223 if( blob_size(ob)<=PROLOG_SIZE && title!=0 && blob_size(title)==0 ){
224 blob_appendb(title, text);
225 return;
226 }
227 INTER_BLOCK(ob);
228 blob_appendf(ob, "<h%d>", level);
229 blob_appendb(ob, text);
230 blob_appendf(ob, "</h%d>", level);
231 }
232
233 static void html_hrule(struct Blob *ob, void *opaque){
234 INTER_BLOCK(ob);
235 blob_append_literal(ob, "<hr />\n");
236 }
237
238
239 static void html_list(
240 struct Blob *ob,
@@ -210,11 +245,11 @@
245 char ol[] = "ol";
246 char ul[] = "ul";
247 char *tag = (flags & MKD_LIST_ORDERED) ? ol : ul;
248 INTER_BLOCK(ob);
249 blob_appendf(ob, "<%s>\n", tag);
250 blob_appendb(ob, text);
251 blob_appendf(ob, "</%s>\n", tag);
252 }
253
254 static void html_list_item(
255 struct Blob *ob,
@@ -223,20 +258,20 @@
258 void *opaque
259 ){
260 char *text_data = blob_buffer(text);
261 size_t text_size = blob_size(text);
262 while( text_size>0 && text_data[text_size-1]=='\n' ) text_size--;
263 blob_append_literal(ob, "<li>");
264 blob_append(ob, text_data, text_size);
265 blob_append_literal(ob, "</li>\n");
266 }
267
268 static void html_paragraph(struct Blob *ob, struct Blob *text, void *opaque){
269 INTER_BLOCK(ob);
270 blob_append_literal(ob, "<p>");
271 blob_appendb(ob, text);
272 blob_append_literal(ob, "</p>\n");
273 }
274
275
276 static void html_table(
277 struct Blob *ob,
@@ -243,71 +278,301 @@
278 struct Blob *head_row,
279 struct Blob *rows,
280 void *opaque
281 ){
282 INTER_BLOCK(ob);
283 blob_append_literal(ob, "<table>\n");
284 if( head_row && blob_size(head_row)>0 ){
285 blob_append_literal(ob, "<thead>\n");
286 blob_appendb(ob, head_row);
287 blob_append_literal(ob, "</thead>\n<tbody>\n");
288 }
289 if( rows ){
290 blob_appendb(ob, rows);
291 }
292 if( head_row && blob_size(head_row)>0 ){
293 blob_append_literal(ob, "</tbody>\n");
294 }
295 blob_append_literal(ob, "</table>\n");
296 }
297
298 static void html_table_cell(
299 struct Blob *ob,
300 struct Blob *text,
301 int flags,
302 void *opaque
303 ){
304 if( flags & MKD_CELL_HEAD ){
305 blob_append_literal(ob, " <th");
306 }else{
307 blob_append_literal(ob, " <td");
308 }
309 switch( flags & MKD_CELL_ALIGN_MASK ){
310 case MKD_CELL_ALIGN_LEFT: {
311 blob_append_literal(ob, " align=\"left\"");
312 break;
313 }
314 case MKD_CELL_ALIGN_RIGHT: {
315 blob_append_literal(ob, " align=\"right\"");
316 break;
317 }
318 case MKD_CELL_ALIGN_CENTER: {
319 blob_append_literal(ob, " align=\"center\"");
320 break;
321 }
322 }
323 blob_append_literal(ob, ">");
324 blob_appendb(ob, text);
325 if( flags & MKD_CELL_HEAD ){
326 blob_append_literal(ob, "</th>\n");
327 }else{
328 blob_append_literal(ob, "</td>\n");
329 }
330 }
331
332 static void html_table_row(
333 struct Blob *ob,
334 struct Blob *cells,
335 int flags,
336 void *opaque
337 ){
338 blob_append_literal(ob, " <tr>\n");
339 blob_appendb(ob, cells);
340 blob_append_literal(ob, " </tr>\n");
341 }
342
343 /*
344 ** Render a token of user provided classes.
345 ** If bHTML is true then render HTML for (presumably) visible text,
346 ** otherwise just a space-separated list of the derived classes.
347 */
348 static void append_footnote_upc(
349 struct Blob *ob,
350 const struct Blob *upc, /* token of user-provided classes */
351 int bHTML
352 ){
353 const char *z = blob_buffer(upc);
354 int i, n = blob_size(upc);
355
356 if( n<3 ) return;
357 assert( z[0]=='.' && z[n-1] == ':' );
358 if( bHTML ){
359 blob_append_literal(ob, "<span class='fn-upc'>"
360 "<span class='fn-upcDot'>.</span>");
361 }
362 n = 0;
363 do{
364 z++;
365 if( *z!='.' && *z!=':' ){
366 assert( fossil_isalnum(*z) || *z=='-' );
367 n++;
368 continue;
369 }
370 assert( n );
371 if( bHTML ) blob_append_literal(ob, "<span class='");
372 blob_append_literal(ob, "fn-upc-");
373
374 for(i=-n; i<0; i++){
375 blob_append_char(ob, fossil_tolower(z[i]) );
376 }
377 if( bHTML ){
378 blob_append_literal(ob, "'>");
379 blob_append(ob, z-n, n);
380 blob_append_literal(ob, "</span>");
381 }else{
382 blob_append_char(ob, ' ');
383 }
384 n = 0;
385 if( bHTML ){
386 if( *z==':' ){
387 blob_append_literal(ob,"<span class='fn-upcColon'>:</span>");
388 }else{
389 blob_append_literal(ob,"<span class='fn-upcDot'>.</span>");
390 }
391 }
392 }while( *z != ':' );
393 if( bHTML ) blob_append_literal(ob,"</span>\n");
394 }
395
396 static int html_footnote_ref(
397 struct Blob *ob, const struct Blob *span, const struct Blob *upc,
398 int iMark, int locus, void *opaque
399 ){
400 const struct MarkdownToHtml* ctx = (struct MarkdownToHtml*)opaque;
401 const bitfield64_t l = to_base26(locus-1,0);
402 char pos[32];
403 memset(pos,0,32);
404 assert( locus > 0 );
405 /* expect BUGs if the following yields compiler warnings */
406 if( iMark > 0 ){ /* a regular reference to a footnote */
407 sprintf(pos, "%s-%d-%s", ctx->unique.c, iMark, l.c);
408 if(span && blob_size(span)) {
409 blob_append_literal(ob,"<span class='");
410 append_footnote_upc(ob, upc, 0);
411 blob_append_literal(ob,"notescope' id='noteref");
412 blob_appendf(ob,"%s'>",pos);
413 blob_appendb(ob, span);
414 blob_trim(ob);
415 blob_append_literal(ob,"<sup class='noteref'><a href='");
416 BLOB_APPEND_URI(ob, ctx);
417 blob_appendf(ob,"#footnote%s'>%d</a></sup></span>", pos, iMark);
418 }else{
419 blob_trim(ob);
420 blob_append_literal(ob,"<sup class='");
421 append_footnote_upc(ob, upc, 0);
422 blob_append_literal(ob,"noteref'><a href='");
423 BLOB_APPEND_URI(ob, ctx);
424 blob_appendf(ob,"#footnote%s' id='noteref%s'>%d</a></sup>",
425 pos, pos, iMark);
426 }
427 }else{ /* misreference */
428 assert( iMark == -1 );
429
430 sprintf(pos, "%s-%s", ctx->unique.c, l.c);
431 if(span && blob_size(span)) {
432 blob_appendf(ob, "<span class='notescope' id='misref%s'>", pos);
433 blob_appendb(ob, span);
434 blob_trim(ob);
435 blob_append_literal(ob, "<sup class='noteref misref'><a href='");
436 BLOB_APPEND_URI(ob, ctx);
437 blob_appendf(ob, "#misreference%s'>misref</a></sup></span>", pos);
438 }else{
439 blob_trim(ob);
440 blob_append_literal(ob, "<sup class='noteref misref'><a href='");
441 BLOB_APPEND_URI(ob, ctx);
442 blob_appendf(ob, "#misreference%s' id='misref%s'>", pos, pos);
443 blob_append_literal(ob, "misref</a></sup>");
444 }
445 }
446 return 1;
447 }
448
449 /* Render a single item of the footnotes list.
450 * Each backref gets a unique id to enable dynamic styling. */
451 static void html_footnote_item(
452 struct Blob *ob, const struct Blob *text, int iMark, int nUsed, void *opaque
453 ){
454 const struct MarkdownToHtml* ctx = (struct MarkdownToHtml*)opaque;
455 const char * const unique = ctx->unique.c;
456 assert( nUsed >= 0 );
457 /* expect BUGs if the following yields compiler warnings */
458
459 if( iMark < 0 ){ /* misreferences */
460 assert( iMark == -1 );
461 assert( nUsed );
462 blob_append_literal(ob,"<li class='fn-misreference'>"
463 "<sup class='fn-backrefs'>");
464 if( nUsed == 1 ){
465 blob_appendf(ob,"<a id='misreference%s-a' href='", unique);
466 BLOB_APPEND_URI(ob, ctx);
467 blob_appendf(ob,"#misref%s-a'>^</a>", unique);
468 }else{
469 int i;
470 blob_append_char(ob, '^');
471 for(i=0; i<nUsed && i<26; i++){
472 const int c = i + (unsigned)'a';
473 blob_appendf(ob," <a id='misreference%s-%c' href='", unique,c);
474 BLOB_APPEND_URI(ob, ctx);
475 blob_appendf(ob,"#misref%s-%c'>%c</a>", unique,c, c);
476 }
477 if( i < nUsed ) blob_append_literal(ob," &hellip;");
478 }
479 blob_append_literal(ob,"</sup>\n<span>Misreference</span>");
480 }else if( iMark > 0 ){ /* regular, joined and overnested footnotes */
481 char pos[24];
482 int bJoin = 0;
483 #define _joined_footnote_indicator "<ul class='fn-joined'>"
484 #define _jfi_sz (sizeof(_joined_footnote_indicator)-1)
485 /* make.footnote_item() invocations should pass args accordingly */
486 const struct Blob *upc = text+1;
487 assert( text );
488 /* allow blob_size(text)==0 for constructs like [...](^ [] ()) */
489 memset(pos,0,24);
490 sprintf(pos, "%s-%d", unique, iMark);
491 blob_appendf(ob, "<li id='footnote%s' class='", pos);
492 if( nUsed ){
493 if( blob_size(text)>=_jfi_sz &&
494 !memcmp(blob_buffer(text),_joined_footnote_indicator,_jfi_sz)){
495 bJoin = 1;
496 blob_append_literal(ob, "fn-joined ");
497 }
498 append_footnote_upc(ob, upc, 0);
499 }else{
500 blob_append_literal(ob, "fn-toodeep ");
501 }
502 if( nUsed <= 1 ){
503 blob_append_literal(ob, "fn-monoref'><sup class='fn-backrefs'>");
504 blob_appendf(ob,"<a id='footnote%s-a' href='", pos);
505 BLOB_APPEND_URI(ob, ctx);
506 blob_appendf(ob,"#noteref%s-a'>^</a>", pos);
507 }else{
508 int i;
509 blob_append_literal(ob, "fn-polyref'><sup class='fn-backrefs'>^");
510 for(i=0; i<nUsed && i<26; i++){
511 const int c = i + (unsigned)'a';
512 blob_appendf(ob," <a id='footnote%s-%c' href='", pos,c);
513 BLOB_APPEND_URI(ob, ctx);
514 blob_appendf(ob,"#noteref%s-%c'>%c</a>", pos,c, c);
515 }
516 /* It's unlikely that so many backrefs will be usefull */
517 /* but maybe for some machine generated documents... */
518 for(; i<nUsed && i<676; i++){
519 const bitfield64_t l = to_base26(i,0);
520 blob_appendf(ob," <a id='footnote%s-%s' href='", pos, l.c);
521 BLOB_APPEND_URI(ob, ctx);
522 blob_appendf(ob,"#noteref%s-%s'>%s</a>", pos,l.c, l.c);
523 }
524 if( i < nUsed ) blob_append_literal(ob," &hellip;");
525 }
526 blob_append_literal(ob,"</sup>\n");
527 if( bJoin ){
528 blob_append_literal(ob,"<sup class='fn-joined'></sup><ul>");
529 blob_append(ob,blob_buffer(text)+_jfi_sz,blob_size(text)-_jfi_sz);
530 }else if( nUsed ){
531 append_footnote_upc(ob, upc, 1);
532 blob_appendb(ob, text);
533 }else{
534 blob_append_literal(ob,"<i></i>\n"
535 "<pre><code class='language-markdown'>");
536 if( blob_size(upc) ){
537 blob_appendb(ob, upc);
538 }
539 html_escape(ob, blob_buffer(text), blob_size(text));
540 blob_append_literal(ob,"</code></pre>");
541 }
542 #undef _joined_footnote_indicator
543 #undef _jfi_sz
544 }else{ /* a footnote was defined but wasn't referenced */
545 /* make.footnote_item() invocations should pass args accordingly */
546 const struct Blob *id = text-1, *upc = text+1;
547 assert( !nUsed );
548 assert( text );
549 assert( blob_size(text) );
550 assert( blob_size(id) );
551 blob_append_literal(ob,"<li class='fn-unreferenced'>\n[^&nbsp;<code>");
552 html_escape(ob, blob_buffer(id), blob_size(id));
553 blob_append_literal(ob, "</code>&nbsp;]<i></i>\n"
554 "<pre><code class='language-markdown'>");
555 if( blob_size(upc) ){
556 blob_appendb(ob, upc);
557 }
558 html_escape(ob, blob_buffer(text), blob_size(text));
559 blob_append_literal(ob,"</code></pre>");
560 }
561 blob_append_literal(ob, "\n</li>\n");
562 }
563
564 static void html_footnotes(
565 struct Blob *ob, const struct Blob *items, void *opaque
566 ){
567 if( items && blob_size(items) ){
568 blob_append_literal(ob,
569 "\n<hr class='footnotes-separator'/>\n<ol class='footnotes'>\n");
570 blob_appendb(ob, items);
571 blob_append_literal(ob, "</ol>\n");
572 }
573 }
574
575 /* HTML span tags */
576
577 static int html_raw_html_tag(struct Blob *ob, struct Blob *text, void *opaque){
578 blob_append(ob, blob_buffer(text), blob_size(text));
@@ -319,21 +584,21 @@
584 struct Blob *link,
585 enum mkd_autolink type,
586 void *opaque
587 ){
588 if( !link || blob_size(link)<=0 ) return 0;
589 blob_append_literal(ob, "<a href=\"");
590 if( type==MKDA_IMPLICIT_EMAIL ) blob_append_literal(ob, "mailto:");
591 html_quote(ob, blob_buffer(link), blob_size(link));
592 blob_append_literal(ob, "\">");
593 if( type==MKDA_EXPLICIT_EMAIL && blob_size(link)>7 ){
594 /* remove "mailto:" from displayed text */
595 html_escape(ob, blob_buffer(link)+7, blob_size(link)-7);
596 }else{
597 html_escape(ob, blob_buffer(link), blob_size(link));
598 }
599 blob_append_literal(ob, "</a>");
600 return 1;
601 }
602
603 /*
604 ** The nSrc bytes at zSrc[] are Pikchr input text (allegedly). Process that
@@ -422,13 +687,13 @@
687 ){
688 if( text==0 ){
689 /* no-op */
690 }else if( nSep<=2 ){
691 /* One or two graves: an in-line code span */
692 blob_append_literal(ob, "<code>");
693 html_escape(ob, blob_buffer(text), blob_size(text));
694 blob_append_literal(ob, "</code>");
695 }else{
696 /* Three or more graves: a fenced code block */
697 int n = blob_size(text);
698 const char *z = blob_buffer(text);
699 int i;
@@ -460,25 +725,25 @@
725 struct Blob *ob,
726 struct Blob *text,
727 char c,
728 void *opaque
729 ){
730 blob_append_literal(ob, "<strong>");
731 blob_appendb(ob, text);
732 blob_append_literal(ob, "</strong>");
733 return 1;
734 }
735
736 static int html_emphasis(
737 struct Blob *ob,
738 struct Blob *text,
739 char c,
740 void *opaque
741 ){
742 blob_append_literal(ob, "<em>");
743 blob_appendb(ob, text);
744 blob_append_literal(ob, "</em>");
745 return 1;
746 }
747
748 static int html_image(
749 struct Blob *ob,
@@ -485,24 +750,24 @@
750 struct Blob *link,
751 struct Blob *title,
752 struct Blob *alt,
753 void *opaque
754 ){
755 blob_append_literal(ob, "<img src=\"");
756 html_quote(ob, blob_buffer(link), blob_size(link));
757 blob_append_literal(ob, "\" alt=\"");
758 html_quote(ob, blob_buffer(alt), blob_size(alt));
759 if( title && blob_size(title)>0 ){
760 blob_append_literal(ob, "\" title=\"");
761 html_quote(ob, blob_buffer(title), blob_size(title));
762 }
763 blob_append_literal(ob, "\" />");
764 return 1;
765 }
766
767 static int html_linebreak(struct Blob *ob, void *opaque){
768 blob_append_literal(ob, "<br />\n");
769 return 1;
770 }
771
772 static int html_link(
773 struct Blob *ob,
@@ -523,13 +788,13 @@
788 WIKI_MARKDOWNLINKS
789 ;
790 wiki_resolve_hyperlink(ob, flags, zLink, zClose, sizeof(zClose), 0, zTitle);
791 }
792 if( blob_size(content)==0 ){
793 if( link ) blob_appendb(ob, link);
794 }else{
795 blob_appendb(ob, content);
796 }
797 blob_append(ob, zClose, -1);
798 return 1;
799 }
800
@@ -537,13 +802,13 @@
802 struct Blob *ob,
803 struct Blob *text,
804 char c,
805 void *opaque
806 ){
807 blob_append_literal(ob, "<strong><em>");
808 blob_appendb(ob, text);
809 blob_append_literal(ob, "</em></strong>");
810 return 1;
811 }
812
813
814 static void html_normal_text(struct Blob *ob, struct Blob *text, void *opaque){
@@ -563,10 +828,11 @@
828 ){
829 struct mkd_renderer html_renderer = {
830 /* prolog and epilog */
831 html_prolog,
832 html_epilog,
833 html_footnotes,
834
835 /* block level elements */
836 html_blockcode,
837 html_blockquote,
838 html_blockhtml,
@@ -576,10 +842,11 @@
842 html_list_item,
843 html_paragraph,
844 html_table,
845 html_table_cell,
846 html_table_row,
847 html_footnote_item,
848
849 /* span level elements */
850 html_autolink,
851 html_codespan,
852 html_double_emphasis,
@@ -587,22 +854,30 @@
854 html_image,
855 html_linebreak,
856 html_link,
857 html_raw_html_tag,
858 html_triple_emphasis,
859 html_footnote_ref,
860
861 /* low level elements */
862 0, /* entity */
863 html_normal_text,
864
865 /* misc. parameters */
866 "*_", /* emph_chars */
867 0 /* opaque */
868 };
869 static int invocation = -1; /* no marker for the first document */
870 static const char* zRU = 0; /* REQUEST_URI with escaped quotes */
871 MarkdownToHtml context;
872 memset(&context, 0, sizeof(context));
873 context.output_title = output_title;
874 context.unique = to_base26(invocation++,1);
875 if( !zRU ) zRU = escape_quotes(PD("REQUEST_URI",""));
876 #ifndef FOOTNOTES_WITHOUT_URI
877 blob_set( &context.reqURI, zRU );
878 #endif
879 html_renderer.opaque = &context;
880 if( output_title ) blob_reset(output_title);
881 blob_reset(output_body);
882 markdown(output_body, input_markdown, &html_renderer);
883 }
884
--- src/style.c
+++ src/style.c
@@ -782,10 +782,17 @@
782782
image_url_var("background");
783783
if( !login_is_nobody() ){
784784
Th_Store("login", g.zLogin);
785785
}
786786
Th_MaybeStore("current_feature", feature_from_page_path(local_zCurrentPage) );
787
+ if( g.ftntsIssues[0] || g.ftntsIssues[1] ||
788
+ g.ftntsIssues[2] || g.ftntsIssues[3] ){
789
+ char buf[80];
790
+ sprintf(&buf[0],"%i %i %i %i",g.ftntsIssues[0],g.ftntsIssues[1],
791
+ g.ftntsIssues[2],g.ftntsIssues[3]);
792
+ Th_Store("footnotes_issues_counters", buf);
793
+ }
787794
}
788795
789796
/*
790797
** Draw the header.
791798
*/
792799
--- src/style.c
+++ src/style.c
@@ -782,10 +782,17 @@
782 image_url_var("background");
783 if( !login_is_nobody() ){
784 Th_Store("login", g.zLogin);
785 }
786 Th_MaybeStore("current_feature", feature_from_page_path(local_zCurrentPage) );
 
 
 
 
 
 
 
787 }
788
789 /*
790 ** Draw the header.
791 */
792
--- src/style.c
+++ src/style.c
@@ -782,10 +782,17 @@
782 image_url_var("background");
783 if( !login_is_nobody() ){
784 Th_Store("login", g.zLogin);
785 }
786 Th_MaybeStore("current_feature", feature_from_page_path(local_zCurrentPage) );
787 if( g.ftntsIssues[0] || g.ftntsIssues[1] ||
788 g.ftntsIssues[2] || g.ftntsIssues[3] ){
789 char buf[80];
790 sprintf(&buf[0],"%i %i %i %i",g.ftntsIssues[0],g.ftntsIssues[1],
791 g.ftntsIssues[2],g.ftntsIssues[3]);
792 Th_Store("footnotes_issues_counters", buf);
793 }
794 }
795
796 /*
797 ** Draw the header.
798 */
799
+14 -2
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -1893,18 +1893,20 @@
18931893
** Usage: %fossil test-markdown-render FILE ...
18941894
**
18951895
** Render markdown in FILE as HTML on stdout.
18961896
** Options:
18971897
**
1898
-** --safe Restrict the output to use only "safe" HTML
1898
+** --safe Restrict the output to use only "safe" HTML
1899
+** --lint-footnotes Print stats for footnotes-related issues
18991900
*/
19001901
void test_markdown_render(void){
19011902
Blob in, out;
19021903
int i;
1903
- int bSafe = 0;
1904
+ int bSafe = 0, bFnLint = 0;
19041905
db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
19051906
bSafe = find_option("safe",0,0)!=0;
1907
+ bFnLint = find_option("lint-footnotes",0,0)!=0;
19061908
verify_all_options();
19071909
for(i=2; i<g.argc; i++){
19081910
blob_zero(&out);
19091911
blob_read_from_file(&in, g.argv[i], ExtFILE);
19101912
if( g.argc>3 ){
@@ -1915,10 +1917,20 @@
19151917
safe_html(&out);
19161918
blob_write_to_file(&out, "-");
19171919
blob_reset(&in);
19181920
blob_reset(&out);
19191921
}
1922
+ if( bFnLint && (g.ftntsIssues[0] || g.ftntsIssues[1]
1923
+ || g.ftntsIssues[2] || g.ftntsIssues[3] )){
1924
+ fossil_fatal("There were issues with footnotes:\n"
1925
+ " %8d misreference%s\n"
1926
+ " %8d unreferenced\n"
1927
+ " %8d splitted\n"
1928
+ " %8d overnested",
1929
+ g.ftntsIssues[0], g.ftntsIssues[0]==1?"":"s",
1930
+ g.ftntsIssues[1], g.ftntsIssues[2], g.ftntsIssues[3]);
1931
+ }
19201932
}
19211933
19221934
/*
19231935
** Search for a <title>...</title> at the beginning of a wiki page.
19241936
** Return true (nonzero) if a title is found. Return zero if there is
19251937
19261938
ADDED test/markdown-test3.md
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -1893,18 +1893,20 @@
1893 ** Usage: %fossil test-markdown-render FILE ...
1894 **
1895 ** Render markdown in FILE as HTML on stdout.
1896 ** Options:
1897 **
1898 ** --safe Restrict the output to use only "safe" HTML
 
1899 */
1900 void test_markdown_render(void){
1901 Blob in, out;
1902 int i;
1903 int bSafe = 0;
1904 db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
1905 bSafe = find_option("safe",0,0)!=0;
 
1906 verify_all_options();
1907 for(i=2; i<g.argc; i++){
1908 blob_zero(&out);
1909 blob_read_from_file(&in, g.argv[i], ExtFILE);
1910 if( g.argc>3 ){
@@ -1915,10 +1917,20 @@
1915 safe_html(&out);
1916 blob_write_to_file(&out, "-");
1917 blob_reset(&in);
1918 blob_reset(&out);
1919 }
 
 
 
 
 
 
 
 
 
 
1920 }
1921
1922 /*
1923 ** Search for a <title>...</title> at the beginning of a wiki page.
1924 ** Return true (nonzero) if a title is found. Return zero if there is
1925
1926 DDED test/markdown-test3.md
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -1893,18 +1893,20 @@
1893 ** Usage: %fossil test-markdown-render FILE ...
1894 **
1895 ** Render markdown in FILE as HTML on stdout.
1896 ** Options:
1897 **
1898 ** --safe Restrict the output to use only "safe" HTML
1899 ** --lint-footnotes Print stats for footnotes-related issues
1900 */
1901 void test_markdown_render(void){
1902 Blob in, out;
1903 int i;
1904 int bSafe = 0, bFnLint = 0;
1905 db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
1906 bSafe = find_option("safe",0,0)!=0;
1907 bFnLint = find_option("lint-footnotes",0,0)!=0;
1908 verify_all_options();
1909 for(i=2; i<g.argc; i++){
1910 blob_zero(&out);
1911 blob_read_from_file(&in, g.argv[i], ExtFILE);
1912 if( g.argc>3 ){
@@ -1915,10 +1917,20 @@
1917 safe_html(&out);
1918 blob_write_to_file(&out, "-");
1919 blob_reset(&in);
1920 blob_reset(&out);
1921 }
1922 if( bFnLint && (g.ftntsIssues[0] || g.ftntsIssues[1]
1923 || g.ftntsIssues[2] || g.ftntsIssues[3] )){
1924 fossil_fatal("There were issues with footnotes:\n"
1925 " %8d misreference%s\n"
1926 " %8d unreferenced\n"
1927 " %8d splitted\n"
1928 " %8d overnested",
1929 g.ftntsIssues[0], g.ftntsIssues[0]==1?"":"s",
1930 g.ftntsIssues[1], g.ftntsIssues[2], g.ftntsIssues[3]);
1931 }
1932 }
1933
1934 /*
1935 ** Search for a <title>...</title> at the beginning of a wiki page.
1936 ** Return true (nonzero) if a title is found. Return zero if there is
1937
1938 DDED test/markdown-test3.md
--- a/test/markdown-test3.md
+++ b/test/markdown-test3.md
@@ -0,0 +1,194 @@
1
+
2
+Markdown Footnotes Test Document
3
+================================
4
+
5
+**This document** should help with testing of footnotes support that
6
+is introduced by the ["`markdown-footnotes`"][branch] branch.
7
+It **might look pretty misformatted unless rendered by the proper Fossil
8
+executable** that incorporates the abovementioned branch.[^1]
9
+That is also a humble attempt to explore the robustness of the Markdown parser.
10
+So please excuse for the mess in the [source code of this document][src].
11
+By no means the normal use of footnotes should look that scarry.
12
+
13
+Developers are invited to add test cases here[^here].
14
+It is suggested that the more simple is a test case the earlier it should
15
+appear in this document.[^ if glitch occurs ]
16
+
17
+
18
+[^lost3]: This note was defined at the begining of the document.
19
+
20
+[^duplicate]: This came from the begining of the document.
21
+
22
+A footnote's label should be case insensitive[^ case INSENSITIVE ],
23
+it is whitespace-savvy and can even contain newlines.[^ a
24
+multil[^].
25
+Markup within [a [text fragment](https://en.wikipedia.org/wiki/Lorem_ipsum)
26
+of a *span-bounded footnote*][^markup] should also be rendered.
27
+
28
+Another reference[^many-refs] to the preveously used footnote.
29
+
30
+[^lost2]: This note was defined in the middle of the document.
31
+ It references [its previous][^lost3]
32
+ and [the forthcoming][^lost1] siblings.
33
+
34
+[^i am strayed]:
35
+ This should be presented **verbatim** (without any [markup][^])
36
+ in the end of the footnotes.
37
+
38
+ Default skin renders label in red font and the main text in gray.
39
+ Other styling may also apply.
40
+
41
+Inline footnotes are supported.(^These may be usefull for adding
42
+<s>small</s> comments.)
43
+
44
+This is a corner case that is rendered as [an empty footnote](^ [] ()).
45
+
46
+If [undefined label is used][^] then red "`misref`" is emited instead of
47
+a numeric marker.[^ see it yourself ]
48
+This can be overridden by the skin though.
49
+
50
+The refenrence at the end of this sentence is the sole reason of
51
+rendering of <s>`lost1` and</s> [lost2][^].
52
+
53
+If several labeled footnote definitions have the same equal label then texts
54
+from all these definitions are joined.[^duplicate]
55
+
56
+Several references should be recognized as several distinct numbers.
57
+(^There should be an interval between numbers.) [^many-refs]
58
+
59
+If markup is ambigous between a span-bounded footnote and
60
+a "free-standing" footnote followed by another footnote
61
+then interpret as the later case.
62
+This facilitates the usage in the usual case
63
+when several footnotes are refenrenced at the end
64
+of a phrase.[^scipub][^many-refs](^All these four should
65
+be parsed as "free-standing" footnotes)[^Coelurosauria]
66
+
67
+An ambiguity between a link to an image and a *free-standing referenced
68
+footnote* should be resolved as a footnote![^not-image]
69
+
70
+A footnote may not be empty(^)
71
+or consist just of blank characters.(^
72
+ )
73
+
74
+The same holds for labeled footnotes. If definition of a labeled footnote
75
+is blank then it is not accepted by the first pass of the parser and
76
+is recognized during the second pass as misreference.
77
+[^ This definition consists of just blanks ]:
78
+
79
+
80
+<style>
81
+ li.fn-upc-example span.fn-upc {
82
+ border: solid 2px lightgreen;
83
+ border-radius: 0.25em;
84
+ padding-left: 2px;
85
+ padding-right: 2px;
86
+ margin-bottom: 0.2em;
87
+ }
88
+ li.fn-upc-example span.fn-upcDot:first-child {
89
+ font-weight: bold;
90
+ }
91
+ sup.noteref.fn-upc-example,
92
+ span.notescope.fn-upc-example sup.noteref {
93
+ border: solid 2px lightgreen;
94
+[^duplicate]:
95
+ Labeled footnote definition may appear anywhere.
96
+ That part came from inside of an inline style definition.
97
+ border-radius: 0.4em;
98
+ padding: 2px;
99
+ }
100
+ sup.noteref.fn-upc-example::after,
101
+ span.notescope.fn-upc-example sup.noteref::after {
102
+ content: " ⛄";
103
+ }
104
+ sup.noteref.fn-upc-example:hover::after,
105
+ span.notescope.fn-upc-example sup.noteref:hover::after {
106
+ content: " 👻";
107
+ }
108
+ li.fn-upc-l span.fn-upc {
109
+ font-size: 60%;
110
+ color: orange;
111
+ }
112
+ li.fn-upc-l span.fn-upc span.fn-upcDot {
113
+ display: none;
114
+ }
115
+</style>
116
+
117
+It is possible to provide a list of classes for a particular footnote and
118
+all its references. This is achieved by prepending a footnote's text with
119
+a special token that starts with dot and ends with colon.
120
+(^
121
+ .alpha-Numeric123.EXAMPLE:
122
+ This token defines a dot-separated list of CSS classes
123
+ which are added to that particular footnote and also to the
124
+ corresponding reference(s). Hypens ('-') are also allowed.
125
+ Classes from the token are tranformed to lowercase and are prepended
126
+ with `"fn-upc-"` to avoid collisions.
127
+)
128
+This feature is "*opt-in*": there is nothing wrong in starting a footnote's
129
+text with a token of that form while not defining any corresponding classes
130
+in the stylesheet.[^nostyle]
131
+If a footnote consists just of a valid userclass token then this token
132
+is not interpreted as such, instead it is emitted as plain text.
133
+(^
134
+ .bare.classlist.inside.inline.footnote:
135
+)[^bare1]
136
+[^bare2]
137
+
138
+[^duplicate]: .with.UPC.token:
139
+ When duplicates are joined their UPC tokens are treated as plain-text.
140
+ Blank characters between token and main text must be preserved.
141
+
142
+<html>
143
+ Click
144
+ <a href="?a=B&quote='&nonASCII=😂&script=<script>alert('Broken!');</script>">
145
+ here</a> and
146
+ <a href='?a=B&quote="&nonASCII=😂&script=<script>alert("Broken!");</script>'>
147
+ here</a>
148
+ to test escaping of REQUEST_URI in the generated footnote markers.
149
+</html>
150
+
151
+A depth of nesting must be limited.
152
+(^
153
+ .L.1: A long chain of nested inline footnotes...
154
+ (^
155
+ .L.2: is a rather unusual thing...
156
+ (^
157
+ .L.3: and requires extra CPU cycles for processing.
158
+ (^
159
+on a certain level.
160
+ (^
161
+ .L.7: A particular value for that limit...
162
+ (^
163
+ is hard-coded in src/markdown.c ...
164
+ (^
165
+ in function `markdown()` ...
166
+ (^
167
+ in variable named `maxDepth`.
168
+ (^
169
+ For the time being, its value is **5**
170
+ )
171
+ )
172
+ )
173
+ )
174
+ )
175
+ )
176
+ )
177
+ )
178
+ )
179
+ )
180
+)
181
+
182
+## Footnotes
183
+
184
+[branch]: /timeline?r=markdown-footnotes&nowiki
185
+
186
+[^' extentootnotes is a Fossil extension of
187
+ Markdown. Your other tools may have limited support for these.
188
+
189
+[^here]: [History of test/markdown-test3.md](/finfo/test/markdown-test3.md)
190
+
191
+[src]: /file/test/markdown-test3.md?ci=markdown-footnotes&txt&ln
192
+
193
+[^if glitch occurs]:
194
+ So that sim
--- a/test/markdown-test3.md
+++ b/test/markdown-test3.md
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/test/markdown-test3.md
+++ b/test/markdown-test3.md
@@ -0,0 +1,194 @@
1
2 Markdown Footnotes Test Document
3 ================================
4
5 **This document** should help with testing of footnotes support that
6 is introduced by the ["`markdown-footnotes`"][branch] branch.
7 It **might look pretty misformatted unless rendered by the proper Fossil
8 executable** that incorporates the abovementioned branch.[^1]
9 That is also a humble attempt to explore the robustness of the Markdown parser.
10 So please excuse for the mess in the [source code of this document][src].
11 By no means the normal use of footnotes should look that scarry.
12
13 Developers are invited to add test cases here[^here].
14 It is suggested that the more simple is a test case the earlier it should
15 appear in this document.[^ if glitch occurs ]
16
17
18 [^lost3]: This note was defined at the begining of the document.
19
20 [^duplicate]: This came from the begining of the document.
21
22 A footnote's label should be case insensitive[^ case INSENSITIVE ],
23 it is whitespace-savvy and can even contain newlines.[^ a
24 multil[^].
25 Markup within [a [text fragment](https://en.wikipedia.org/wiki/Lorem_ipsum)
26 of a *span-bounded footnote*][^markup] should also be rendered.
27
28 Another reference[^many-refs] to the preveously used footnote.
29
30 [^lost2]: This note was defined in the middle of the document.
31 It references [its previous][^lost3]
32 and [the forthcoming][^lost1] siblings.
33
34 [^i am strayed]:
35 This should be presented **verbatim** (without any [markup][^])
36 in the end of the footnotes.
37
38 Default skin renders label in red font and the main text in gray.
39 Other styling may also apply.
40
41 Inline footnotes are supported.(^These may be usefull for adding
42 <s>small</s> comments.)
43
44 This is a corner case that is rendered as [an empty footnote](^ [] ()).
45
46 If [undefined label is used][^] then red "`misref`" is emited instead of
47 a numeric marker.[^ see it yourself ]
48 This can be overridden by the skin though.
49
50 The refenrence at the end of this sentence is the sole reason of
51 rendering of <s>`lost1` and</s> [lost2][^].
52
53 If several labeled footnote definitions have the same equal label then texts
54 from all these definitions are joined.[^duplicate]
55
56 Several references should be recognized as several distinct numbers.
57 (^There should be an interval between numbers.) [^many-refs]
58
59 If markup is ambigous between a span-bounded footnote and
60 a "free-standing" footnote followed by another footnote
61 then interpret as the later case.
62 This facilitates the usage in the usual case
63 when several footnotes are refenrenced at the end
64 of a phrase.[^scipub][^many-refs](^All these four should
65 be parsed as "free-standing" footnotes)[^Coelurosauria]
66
67 An ambiguity between a link to an image and a *free-standing referenced
68 footnote* should be resolved as a footnote![^not-image]
69
70 A footnote may not be empty(^)
71 or consist just of blank characters.(^
72 )
73
74 The same holds for labeled footnotes. If definition of a labeled footnote
75 is blank then it is not accepted by the first pass of the parser and
76 is recognized during the second pass as misreference.
77 [^ This definition consists of just blanks ]:
78
79
80 <style>
81 li.fn-upc-example span.fn-upc {
82 border: solid 2px lightgreen;
83 border-radius: 0.25em;
84 padding-left: 2px;
85 padding-right: 2px;
86 margin-bottom: 0.2em;
87 }
88 li.fn-upc-example span.fn-upcDot:first-child {
89 font-weight: bold;
90 }
91 sup.noteref.fn-upc-example,
92 span.notescope.fn-upc-example sup.noteref {
93 border: solid 2px lightgreen;
94 [^duplicate]:
95 Labeled footnote definition may appear anywhere.
96 That part came from inside of an inline style definition.
97 border-radius: 0.4em;
98 padding: 2px;
99 }
100 sup.noteref.fn-upc-example::after,
101 span.notescope.fn-upc-example sup.noteref::after {
102 content: " ⛄";
103 }
104 sup.noteref.fn-upc-example:hover::after,
105 span.notescope.fn-upc-example sup.noteref:hover::after {
106 content: " 👻";
107 }
108 li.fn-upc-l span.fn-upc {
109 font-size: 60%;
110 color: orange;
111 }
112 li.fn-upc-l span.fn-upc span.fn-upcDot {
113 display: none;
114 }
115 </style>
116
117 It is possible to provide a list of classes for a particular footnote and
118 all its references. This is achieved by prepending a footnote's text with
119 a special token that starts with dot and ends with colon.
120 (^
121 .alpha-Numeric123.EXAMPLE:
122 This token defines a dot-separated list of CSS classes
123 which are added to that particular footnote and also to the
124 corresponding reference(s). Hypens ('-') are also allowed.
125 Classes from the token are tranformed to lowercase and are prepended
126 with `"fn-upc-"` to avoid collisions.
127 )
128 This feature is "*opt-in*": there is nothing wrong in starting a footnote's
129 text with a token of that form while not defining any corresponding classes
130 in the stylesheet.[^nostyle]
131 If a footnote consists just of a valid userclass token then this token
132 is not interpreted as such, instead it is emitted as plain text.
133 (^
134 .bare.classlist.inside.inline.footnote:
135 )[^bare1]
136 [^bare2]
137
138 [^duplicate]: .with.UPC.token:
139 When duplicates are joined their UPC tokens are treated as plain-text.
140 Blank characters between token and main text must be preserved.
141
142 <html>
143 Click
144 <a href="?a=B&quote='&nonASCII=😂&script=<script>alert('Broken!');</script>">
145 here</a> and
146 <a href='?a=B&quote="&nonASCII=😂&script=<script>alert("Broken!");</script>'>
147 here</a>
148 to test escaping of REQUEST_URI in the generated footnote markers.
149 </html>
150
151 A depth of nesting must be limited.
152 (^
153 .L.1: A long chain of nested inline footnotes...
154 (^
155 .L.2: is a rather unusual thing...
156 (^
157 .L.3: and requires extra CPU cycles for processing.
158 (^
159 on a certain level.
160 (^
161 .L.7: A particular value for that limit...
162 (^
163 is hard-coded in src/markdown.c ...
164 (^
165 in function `markdown()` ...
166 (^
167 in variable named `maxDepth`.
168 (^
169 For the time being, its value is **5**
170 )
171 )
172 )
173 )
174 )
175 )
176 )
177 )
178 )
179 )
180 )
181
182 ## Footnotes
183
184 [branch]: /timeline?r=markdown-footnotes&nowiki
185
186 [^' extentootnotes is a Fossil extension of
187 Markdown. Your other tools may have limited support for these.
188
189 [^here]: [History of test/markdown-test3.md](/finfo/test/markdown-test3.md)
190
191 [src]: /file/test/markdown-test3.md?ci=markdown-footnotes&txt&ln
192
193 [^if glitch occurs]:
194 So that sim
--- www/changes.wiki
+++ www/changes.wiki
@@ -10,10 +10,14 @@
1010
and promote better situational awareness.
1111
* Performance enhancement for the
1212
[./checkin_names.wiki#root|"root:BRANCHNAME" style of tag],
1313
accomplished using a Common Table Expression in the underlying SQL.
1414
* Add the new "[/help?cmd=describe|fossil describe]" command.
15
+ * Markdown subsystem extended with [../src/markdown.md#ftnts|footnotes support].
16
+ See corresponding [../test/markdown-test3.md|test cases],
17
+ [/wiki?name=branch/markdown-footnotes#il|known limitations] and
18
+ [forum:/forumthread/ee1f1597e46ec07a|discussion].
1519
1620
<h2 id='v2_18'>Changes for version 2.18 (2022-02-23)</h2>
1721
* Added support for [./ssl-server.md|SSL/TLS server mode] for commands
1822
like "[/help?cmd=server|fossil server]" and "[/help?cmd=http|fossil http]"
1923
* The new [/help?cmd=cherry-pick|cherry-pick command] is an alias for
2024
--- www/changes.wiki
+++ www/changes.wiki
@@ -10,10 +10,14 @@
10 and promote better situational awareness.
11 * Performance enhancement for the
12 [./checkin_names.wiki#root|"root:BRANCHNAME" style of tag],
13 accomplished using a Common Table Expression in the underlying SQL.
14 * Add the new "[/help?cmd=describe|fossil describe]" command.
 
 
 
 
15
16 <h2 id='v2_18'>Changes for version 2.18 (2022-02-23)</h2>
17 * Added support for [./ssl-server.md|SSL/TLS server mode] for commands
18 like "[/help?cmd=server|fossil server]" and "[/help?cmd=http|fossil http]"
19 * The new [/help?cmd=cherry-pick|cherry-pick command] is an alias for
20
--- www/changes.wiki
+++ www/changes.wiki
@@ -10,10 +10,14 @@
10 and promote better situational awareness.
11 * Performance enhancement for the
12 [./checkin_names.wiki#root|"root:BRANCHNAME" style of tag],
13 accomplished using a Common Table Expression in the underlying SQL.
14 * Add the new "[/help?cmd=describe|fossil describe]" command.
15 * Markdown subsystem extended with [../src/markdown.md#ftnts|footnotes support].
16 See corresponding [../test/markdown-test3.md|test cases],
17 [/wiki?name=branch/markdown-footnotes#il|known limitations] and
18 [forum:/forumthread/ee1f1597e46ec07a|discussion].
19
20 <h2 id='v2_18'>Changes for version 2.18 (2022-02-23)</h2>
21 * Added support for [./ssl-server.md|SSL/TLS server mode] for commands
22 like "[/help?cmd=server|fossil server]" and "[/help?cmd=http|fossil http]"
23 * The new [/help?cmd=cherry-pick|cherry-pick command] is an alias for
24
--- www/changes.wiki
+++ www/changes.wiki
@@ -10,10 +10,14 @@
1010
and promote better situational awareness.
1111
* Performance enhancement for the
1212
[./checkin_names.wiki#root|"root:BRANCHNAME" style of tag],
1313
accomplished using a Common Table Expression in the underlying SQL.
1414
* Add the new "[/help?cmd=describe|fossil describe]" command.
15
+ * Markdown subsystem extended with [../src/markdown.md#ftnts|footnotes support].
16
+ See corresponding [../test/markdown-test3.md|test cases],
17
+ [/wiki?name=branch/markdown-footnotes#il|known limitations] and
18
+ [forum:/forumthread/ee1f1597e46ec07a|discussion].
1519
1620
<h2 id='v2_18'>Changes for version 2.18 (2022-02-23)</h2>
1721
* Added support for [./ssl-server.md|SSL/TLS server mode] for commands
1822
like "[/help?cmd=server|fossil server]" and "[/help?cmd=http|fossil http]"
1923
* The new [/help?cmd=cherry-pick|cherry-pick command] is an alias for
2024
--- www/changes.wiki
+++ www/changes.wiki
@@ -10,10 +10,14 @@
10 and promote better situational awareness.
11 * Performance enhancement for the
12 [./checkin_names.wiki#root|"root:BRANCHNAME" style of tag],
13 accomplished using a Common Table Expression in the underlying SQL.
14 * Add the new "[/help?cmd=describe|fossil describe]" command.
 
 
 
 
15
16 <h2 id='v2_18'>Changes for version 2.18 (2022-02-23)</h2>
17 * Added support for [./ssl-server.md|SSL/TLS server mode] for commands
18 like "[/help?cmd=server|fossil server]" and "[/help?cmd=http|fossil http]"
19 * The new [/help?cmd=cherry-pick|cherry-pick command] is an alias for
20
--- www/changes.wiki
+++ www/changes.wiki
@@ -10,10 +10,14 @@
10 and promote better situational awareness.
11 * Performance enhancement for the
12 [./checkin_names.wiki#root|"root:BRANCHNAME" style of tag],
13 accomplished using a Common Table Expression in the underlying SQL.
14 * Add the new "[/help?cmd=describe|fossil describe]" command.
15 * Markdown subsystem extended with [../src/markdown.md#ftnts|footnotes support].
16 See corresponding [../test/markdown-test3.md|test cases],
17 [/wiki?name=branch/markdown-footnotes#il|known limitations] and
18 [forum:/forumthread/ee1f1597e46ec07a|discussion].
19
20 <h2 id='v2_18'>Changes for version 2.18 (2022-02-23)</h2>
21 * Added support for [./ssl-server.md|SSL/TLS server mode] for commands
22 like "[/help?cmd=server|fossil server]" and "[/help?cmd=http|fossil http]"
23 * The new [/help?cmd=cherry-pick|cherry-pick command] is an alias for
24

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button