Fossil SCM
Add the WIKI_LINKSONLY formatting option for the wiki formatter.
Commit
4615bc8faf85010b7f5e0444be1fad713e5fd095
Parent
64231a679d0e1da…
2 files changed
+34
+48
-32
+34
| --- src/encode.c | ||
| +++ src/encode.c | ||
| @@ -83,10 +83,44 @@ | ||
| 83 | 83 | zIn++; |
| 84 | 84 | } |
| 85 | 85 | zOut[i] = 0; |
| 86 | 86 | return zOut; |
| 87 | 87 | } |
| 88 | + | |
| 89 | +/* | |
| 90 | +** Append HTML-escaped text to a Blob. | |
| 91 | +*/ | |
| 92 | +void htmlize_to_blob(Blob *p, const char *zIn, int n){ | |
| 93 | + int c, i, j; | |
| 94 | + if( n<0 ) n = strlen(zIn); | |
| 95 | + for(i=j=0; i<n; i++){ | |
| 96 | + c = zIn[i]; | |
| 97 | + switch( c ){ | |
| 98 | + case '<': | |
| 99 | + if( j<i ) blob_append(p, zIn+j, i-j); | |
| 100 | + blob_append(p, "<", 4); | |
| 101 | + j = i+1; | |
| 102 | + break; | |
| 103 | + case '>': | |
| 104 | + if( j<i ) blob_append(p, zIn+j, i-j); | |
| 105 | + blob_append(p, ">", 4); | |
| 106 | + j = i+1; | |
| 107 | + break; | |
| 108 | + case '&': | |
| 109 | + if( j<i ) blob_append(p, zIn+j, i-j); | |
| 110 | + blob_append(p, "&", 5); | |
| 111 | + j = i+1; | |
| 112 | + break; | |
| 113 | + case '"': | |
| 114 | + if( j<i ) blob_append(p, zIn+j, i-j); | |
| 115 | + blob_append(p, """, 6); | |
| 116 | + j = i+1; | |
| 117 | + break; | |
| 118 | + } | |
| 119 | + } | |
| 120 | + if( j<i ) blob_append(p, zIn+j, i-j); | |
| 121 | +} | |
| 88 | 122 | |
| 89 | 123 | |
| 90 | 124 | /* |
| 91 | 125 | ** Encode a string for HTTP. This means converting lots of |
| 92 | 126 | ** characters into the "%HH" where H is a hex digit. It also |
| 93 | 127 |
| --- src/encode.c | |
| +++ src/encode.c | |
| @@ -83,10 +83,44 @@ | |
| 83 | zIn++; |
| 84 | } |
| 85 | zOut[i] = 0; |
| 86 | return zOut; |
| 87 | } |
| 88 | |
| 89 | |
| 90 | /* |
| 91 | ** Encode a string for HTTP. This means converting lots of |
| 92 | ** characters into the "%HH" where H is a hex digit. It also |
| 93 |
| --- src/encode.c | |
| +++ src/encode.c | |
| @@ -83,10 +83,44 @@ | |
| 83 | zIn++; |
| 84 | } |
| 85 | zOut[i] = 0; |
| 86 | return zOut; |
| 87 | } |
| 88 | |
| 89 | /* |
| 90 | ** Append HTML-escaped text to a Blob. |
| 91 | */ |
| 92 | void htmlize_to_blob(Blob *p, const char *zIn, int n){ |
| 93 | int c, i, j; |
| 94 | if( n<0 ) n = strlen(zIn); |
| 95 | for(i=j=0; i<n; i++){ |
| 96 | c = zIn[i]; |
| 97 | switch( c ){ |
| 98 | case '<': |
| 99 | if( j<i ) blob_append(p, zIn+j, i-j); |
| 100 | blob_append(p, "<", 4); |
| 101 | j = i+1; |
| 102 | break; |
| 103 | case '>': |
| 104 | if( j<i ) blob_append(p, zIn+j, i-j); |
| 105 | blob_append(p, ">", 4); |
| 106 | j = i+1; |
| 107 | break; |
| 108 | case '&': |
| 109 | if( j<i ) blob_append(p, zIn+j, i-j); |
| 110 | blob_append(p, "&", 5); |
| 111 | j = i+1; |
| 112 | break; |
| 113 | case '"': |
| 114 | if( j<i ) blob_append(p, zIn+j, i-j); |
| 115 | blob_append(p, """, 6); |
| 116 | j = i+1; |
| 117 | break; |
| 118 | } |
| 119 | } |
| 120 | if( j<i ) blob_append(p, zIn+j, i-j); |
| 121 | } |
| 122 | |
| 123 | |
| 124 | /* |
| 125 | ** Encode a string for HTTP. This means converting lots of |
| 126 | ** characters into the "%HH" where H is a hex digit. It also |
| 127 |
+48
-32
| --- src/wikiformat.c | ||
| +++ src/wikiformat.c | ||
| @@ -23,17 +23,16 @@ | ||
| 23 | 23 | |
| 24 | 24 | #if INTERFACE |
| 25 | 25 | /* |
| 26 | 26 | ** Allowed wiki transformation operations |
| 27 | 27 | */ |
| 28 | -#define WIKI_NOFOLLOW 0x001 | |
| 29 | -#define WIKI_HTML 0x002 | |
| 30 | -#define WIKI_INLINE 0x004 /* Do not surround with <p>..</p> */ | |
| 31 | -#define WIKI_NOBLOCK 0x008 /* No block markup of any kind */ | |
| 32 | -#define WIKI_BUTTONS 0x010 /* Allow sub-menu buttons */ | |
| 33 | -#define WIKI_NOBADLINKS 0x020 /* Ignore broken hyperlinks */ | |
| 34 | -#define WIKI_DECORATEONLY 0x040 /* No markup. Only decorate links */ | |
| 28 | +#define WIKI_HTMLONLY 0x001 /* HTML markup only. No wiki */ | |
| 29 | +#define WIKI_INLINE 0x002 /* Do not surround with <p>..</p> */ | |
| 30 | +#define WIKI_NOBLOCK 0x004 /* No block markup of any kind */ | |
| 31 | +#define WIKI_BUTTONS 0x008 /* Allow sub-menu buttons */ | |
| 32 | +#define WIKI_NOBADLINKS 0x010 /* Ignore broken hyperlinks */ | |
| 33 | +#define WIKI_LINKSONLY 0x020 /* No markup. Only decorate links */ | |
| 35 | 34 | #endif |
| 36 | 35 | |
| 37 | 36 | |
| 38 | 37 | /* |
| 39 | 38 | ** These are the only markup attributes allowed. |
| @@ -389,11 +388,10 @@ | ||
| 389 | 388 | #define AT_PARAGRAPH 0x0020000 /* At start of a paragraph */ |
| 390 | 389 | #define ALLOW_WIKI 0x0040000 /* Allow wiki markup */ |
| 391 | 390 | #define FONT_MARKUP_ONLY 0x0080000 /* Only allow MUTYPE_FONT markup */ |
| 392 | 391 | #define INLINE_MARKUP_ONLY 0x0100000 /* Allow only "inline" markup */ |
| 393 | 392 | #define IN_LIST 0x0200000 /* Within wiki <ul> or <ol> */ |
| 394 | -#define WIKI_USE_HTML 0x0400000 /* wiki-use-html option = on */ | |
| 395 | 393 | |
| 396 | 394 | /* |
| 397 | 395 | ** Current state of the rendering engine |
| 398 | 396 | */ |
| 399 | 397 | typedef struct Renderer Renderer; |
| @@ -985,10 +983,11 @@ | ||
| 985 | 983 | /* |
| 986 | 984 | ** Begin a new paragraph if that something that is needed. |
| 987 | 985 | */ |
| 988 | 986 | static void startAutoParagraph(Renderer *p){ |
| 989 | 987 | if( p->wantAutoParagraph==0 ) return; |
| 988 | + if( p->state & WIKI_LINKSONLY ) return; | |
| 990 | 989 | if( p->wikiList==MARKUP_OL || p->wikiList==MARKUP_UL ) return; |
| 991 | 990 | blob_appendf(p->pOut, "<p>", -1); |
| 992 | 991 | pushStack(p, MARKUP_P); |
| 993 | 992 | p->wantAutoParagraph = 0; |
| 994 | 993 | p->inAutoParagraph = 1; |
| @@ -1147,19 +1146,14 @@ | ||
| 1147 | 1146 | || strncmp(zTarget, "https:", 6)==0 |
| 1148 | 1147 | || strncmp(zTarget, "ftp:", 4)==0 |
| 1149 | 1148 | || strncmp(zTarget, "mailto:", 7)==0 |
| 1150 | 1149 | ){ |
| 1151 | 1150 | blob_appendf(p->pOut, "<a href=\"%s\">", zTarget); |
| 1152 | - /* zTerm = "⟾</a>"; // doesn't work on windows */ | |
| 1153 | 1151 | }else if( zTarget[0]=='/' ){ |
| 1154 | 1152 | blob_appendf(p->pOut, "<a href=\"%s%h\">", g.zTop, zTarget); |
| 1155 | 1153 | }else if( zTarget[0]=='.' || zTarget[0]=='#' ){ |
| 1156 | - if( 1 ){ | |
| 1157 | - blob_appendf(p->pOut, "<a href=\"%h\">", zTarget); | |
| 1158 | - }else{ | |
| 1159 | - zTerm = ""; | |
| 1160 | - } | |
| 1154 | + blob_appendf(p->pOut, "<a href=\"%h\">", zTarget); | |
| 1161 | 1155 | }else if( is_valid_uuid(zTarget) ){ |
| 1162 | 1156 | int isClosed = 0; |
| 1163 | 1157 | if( is_ticket(zTarget, &isClosed) ){ |
| 1164 | 1158 | /* Special display processing for tickets. Display the hyperlink |
| 1165 | 1159 | ** as crossed out if the ticket is closed. |
| @@ -1183,11 +1177,11 @@ | ||
| 1183 | 1177 | blob_appendf(p->pOut, "["); |
| 1184 | 1178 | zTerm = "]"; |
| 1185 | 1179 | } |
| 1186 | 1180 | } |
| 1187 | 1181 | }else if( !in_this_repo(zTarget) ){ |
| 1188 | - if( (p->state & (WIKI_DECORATEONLY|WIKI_NOBADLINKS))!=0 ){ | |
| 1182 | + if( (p->state & (WIKI_LINKSONLY|WIKI_NOBADLINKS))!=0 ){ | |
| 1189 | 1183 | zTerm = ""; |
| 1190 | 1184 | }else{ |
| 1191 | 1185 | blob_appendf(p->pOut, "<span class=\"brokenlink\">[", zTarget); |
| 1192 | 1186 | zTerm = "]</span>"; |
| 1193 | 1187 | } |
| @@ -1201,11 +1195,11 @@ | ||
| 1201 | 1195 | }else if( (z = validWikiPageName(p, zTarget))!=0 ){ |
| 1202 | 1196 | blob_appendf(p->pOut, "<a href=\"%R/wiki?name=%T\">", z); |
| 1203 | 1197 | }else if( zTarget>=&zOrig[2] && !fossil_isspace(zTarget[-2]) ){ |
| 1204 | 1198 | /* Probably an array subscript in code */ |
| 1205 | 1199 | zTerm = ""; |
| 1206 | - }else if( (p->state & (WIKI_NOBADLINKS|WIKI_DECORATEONLY))!=0 ){ | |
| 1200 | + }else if( (p->state & (WIKI_NOBADLINKS|WIKI_LINKSONLY))!=0 ){ | |
| 1207 | 1201 | zTerm = ""; |
| 1208 | 1202 | }else{ |
| 1209 | 1203 | blob_appendf(p->pOut, "<span class=\"brokenlink\">[%h]", zTarget); |
| 1210 | 1204 | zTerm = "</span>"; |
| 1211 | 1205 | } |
| @@ -1245,19 +1239,20 @@ | ||
| 1245 | 1239 | static void wiki_render(Renderer *p, char *z){ |
| 1246 | 1240 | int tokenType; |
| 1247 | 1241 | ParsedMarkup markup; |
| 1248 | 1242 | int n; |
| 1249 | 1243 | int inlineOnly = (p->state & INLINE_MARKUP_ONLY)!=0; |
| 1250 | - int wikiUseHtml = (p->state & WIKI_USE_HTML)!=0; | |
| 1244 | + int wikiHtmlOnly = (p->state & (WIKI_HTMLONLY | WIKI_LINKSONLY))!=0; | |
| 1245 | + int linksOnly = (p->state & WIKI_LINKSONLY)!=0; | |
| 1251 | 1246 | char *zOrig = z; |
| 1252 | 1247 | |
| 1253 | 1248 | /* Make sure the attribute constants and names still align |
| 1254 | 1249 | ** following changes in the attribute list. */ |
| 1255 | 1250 | assert( fossil_strcmp(aAttribute[ATTR_WIDTH].zName, "width")==0 ); |
| 1256 | 1251 | |
| 1257 | 1252 | while( z[0] ){ |
| 1258 | - if( wikiUseHtml ){ | |
| 1253 | + if( wikiHtmlOnly ){ | |
| 1259 | 1254 | n = nextRawToken(z, p, &tokenType); |
| 1260 | 1255 | }else{ |
| 1261 | 1256 | n = nextWikiToken(z, p, &tokenType); |
| 1262 | 1257 | } |
| 1263 | 1258 | p->state &= ~(AT_NEWLINE|AT_PARAGRAPH); |
| @@ -1374,33 +1369,37 @@ | ||
| 1374 | 1369 | startAutoParagraph(p); |
| 1375 | 1370 | zTarget = &z[1]; |
| 1376 | 1371 | for(i=1; z[i] && z[i]!=']'; i++){ |
| 1377 | 1372 | if( z[i]=='|' && zDisplay==0 ){ |
| 1378 | 1373 | zDisplay = &z[i+1]; |
| 1379 | - iS1 = i; | |
| 1380 | - cS1 = z[i]; | |
| 1381 | - z[i] = 0; | |
| 1382 | - for(j=i-1; j>0 && fossil_isspace(z[j]); j--){ z[j] = 0; } | |
| 1374 | + for(j=i; j>0 && fossil_isspace(z[j-1]); j--){} | |
| 1375 | + iS1 = j; | |
| 1376 | + cS1 = z[j]; | |
| 1377 | + z[j] = 0; | |
| 1383 | 1378 | } |
| 1384 | 1379 | } |
| 1385 | 1380 | z[i] = 0; |
| 1386 | 1381 | if( zDisplay==0 ){ |
| 1387 | 1382 | zDisplay = zTarget; |
| 1388 | 1383 | }else{ |
| 1389 | 1384 | while( fossil_isspace(*zDisplay) ) zDisplay++; |
| 1390 | 1385 | } |
| 1391 | 1386 | openHyperlink(p, zTarget, zClose, sizeof(zClose), zOrig); |
| 1392 | - if( zClose[0] ){ | |
| 1387 | + if( linksOnly || zClose[0]==0 ){ | |
| 1388 | + if( cS1 ) z[iS1] = cS1; | |
| 1389 | + if( zClose[0]!=']' ){ | |
| 1390 | + blob_appendf(p->pOut, "[%h]%s", zTarget, zClose); | |
| 1391 | + }else{ | |
| 1392 | + blob_appendf(p->pOut, "%h%s", zTarget, zClose); | |
| 1393 | + } | |
| 1394 | + }else{ | |
| 1393 | 1395 | savedState = p->state; |
| 1394 | 1396 | p->state &= ~ALLOW_WIKI; |
| 1395 | 1397 | p->state |= FONT_MARKUP_ONLY; |
| 1396 | 1398 | wiki_render(p, zDisplay); |
| 1397 | 1399 | p->state = savedState; |
| 1398 | 1400 | blob_append(p->pOut, zClose, -1); |
| 1399 | - }else{ | |
| 1400 | - if( cS1 ) z[iS1] = cS1; | |
| 1401 | - blob_appendf(p->pOut, "[%h]", zTarget); | |
| 1402 | 1401 | } |
| 1403 | 1402 | break; |
| 1404 | 1403 | } |
| 1405 | 1404 | case TOKEN_TEXT: { |
| 1406 | 1405 | int i; |
| @@ -1408,11 +1407,15 @@ | ||
| 1408 | 1407 | if( i<n ) startAutoParagraph(p); |
| 1409 | 1408 | blob_append(p->pOut, z, n); |
| 1410 | 1409 | break; |
| 1411 | 1410 | } |
| 1412 | 1411 | case TOKEN_RAW: { |
| 1413 | - blob_append(p->pOut, z, n); | |
| 1412 | + if( linksOnly ){ | |
| 1413 | + htmlize_to_blob(p->pOut, z, n); | |
| 1414 | + }else{ | |
| 1415 | + blob_append(p->pOut, z, n); | |
| 1416 | + } | |
| 1414 | 1417 | break; |
| 1415 | 1418 | } |
| 1416 | 1419 | case TOKEN_MARKUP: { |
| 1417 | 1420 | const char *zId; |
| 1418 | 1421 | int iDiv; |
| @@ -1606,24 +1609,26 @@ | ||
| 1606 | 1609 | renderer.wantAutoParagraph = 0; |
| 1607 | 1610 | }else{ |
| 1608 | 1611 | renderer.wantAutoParagraph = 1; |
| 1609 | 1612 | } |
| 1610 | 1613 | if( wikiUsesHtml() ){ |
| 1611 | - renderer.state |= WIKI_USE_HTML; | |
| 1614 | + renderer.state |= WIKI_HTMLONLY; | |
| 1612 | 1615 | } |
| 1613 | 1616 | if( pOut ){ |
| 1614 | 1617 | renderer.pOut = pOut; |
| 1615 | 1618 | }else{ |
| 1616 | 1619 | renderer.pOut = cgi_output_blob(); |
| 1617 | 1620 | } |
| 1618 | 1621 | |
| 1619 | 1622 | blob_strip_bom(pIn, 0); |
| 1623 | + if( flags & WIKI_LINKSONLY ) blob_append(renderer.pOut, "<pre>", 5); | |
| 1620 | 1624 | wiki_render(&renderer, blob_str(pIn)); |
| 1621 | 1625 | endAutoParagraph(&renderer); |
| 1622 | 1626 | while( renderer.nStack ){ |
| 1623 | 1627 | popStack(&renderer); |
| 1624 | 1628 | } |
| 1629 | + if( flags & WIKI_LINKSONLY ) blob_append(renderer.pOut, "</pre>", 5); | |
| 1625 | 1630 | blob_append(renderer.pOut, "\n", 1); |
| 1626 | 1631 | free(renderer.aStack); |
| 1627 | 1632 | } |
| 1628 | 1633 | |
| 1629 | 1634 | /* |
| @@ -1631,15 +1636,26 @@ | ||
| 1631 | 1636 | ** |
| 1632 | 1637 | ** %fossil test-wiki-render FILE [OPTIONS] |
| 1633 | 1638 | ** |
| 1634 | 1639 | ** Options: |
| 1635 | 1640 | ** --buttons Set the WIKI_BUTTONS flag |
| 1641 | +** --htmlonly Set the WIKI_HTMLONLY flag | |
| 1642 | +** --linksonly Set the WIKI_LINKSONLY flag | |
| 1643 | +** --nobadlinks Set the WIKI_NOBADLINKS flag | |
| 1644 | +** --inline Set the WIKI_INLINE flag | |
| 1645 | +** --noblock Set the WIKI_NOBLOCK flag | |
| 1636 | 1646 | */ |
| 1637 | 1647 | void test_wiki_render(void){ |
| 1638 | 1648 | Blob in, out; |
| 1639 | 1649 | int flags = 0; |
| 1640 | 1650 | if( find_option("buttons",0,0)!=0 ) flags |= WIKI_BUTTONS; |
| 1651 | + if( find_option("htmlonly",0,0)!=0 ) flags |= WIKI_HTMLONLY; | |
| 1652 | + if( find_option("linksonly",0,0)!=0 ) flags |= WIKI_LINKSONLY; | |
| 1653 | + if( find_option("nobadlinks",0,0)!=0 ) flags |= WIKI_NOBADLINKS; | |
| 1654 | + if( find_option("inline",0,0)!=0 ) flags |= WIKI_INLINE; | |
| 1655 | + if( find_option("noblock",0,0)!=0 ) flags |= WIKI_NOBLOCK; | |
| 1656 | + db_find_and_open_repository(0,0); | |
| 1641 | 1657 | verify_all_options(); |
| 1642 | 1658 | if( g.argc!=3 ) usage("FILE"); |
| 1643 | 1659 | blob_zero(&out); |
| 1644 | 1660 | blob_read_from_file(&in, g.argv[2]); |
| 1645 | 1661 | wiki_convert(&in, &out, flags); |
| @@ -1694,29 +1710,29 @@ | ||
| 1694 | 1710 | Renderer renderer; |
| 1695 | 1711 | int tokenType; |
| 1696 | 1712 | ParsedMarkup markup; |
| 1697 | 1713 | int n; |
| 1698 | 1714 | int inlineOnly; |
| 1699 | - int wikiUseHtml = 0; | |
| 1715 | + int wikiHtmlOnly = 0; | |
| 1700 | 1716 | |
| 1701 | 1717 | memset(&renderer, 0, sizeof(renderer)); |
| 1702 | 1718 | renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH; |
| 1703 | 1719 | if( flags & WIKI_NOBLOCK ){ |
| 1704 | 1720 | renderer.state |= INLINE_MARKUP_ONLY; |
| 1705 | 1721 | } |
| 1706 | 1722 | if( wikiUsesHtml() ){ |
| 1707 | - renderer.state |= WIKI_USE_HTML; | |
| 1708 | - wikiUseHtml = 1; | |
| 1723 | + renderer.state |= WIKI_HTMLONLY; | |
| 1724 | + wikiHtmlOnly = 1; | |
| 1709 | 1725 | } |
| 1710 | 1726 | inlineOnly = (renderer.state & INLINE_MARKUP_ONLY)!=0; |
| 1711 | 1727 | if( replaceFlag ){ |
| 1712 | 1728 | db_multi_exec("DELETE FROM backlink WHERE srctype=%d AND srcid=%d", |
| 1713 | 1729 | srctype, srcid); |
| 1714 | 1730 | } |
| 1715 | 1731 | |
| 1716 | 1732 | while( z[0] ){ |
| 1717 | - if( wikiUseHtml ){ | |
| 1733 | + if( wikiHtmlOnly ){ | |
| 1718 | 1734 | n = nextRawToken(z, &renderer, &tokenType); |
| 1719 | 1735 | }else{ |
| 1720 | 1736 | n = nextWikiToken(z, &renderer, &tokenType); |
| 1721 | 1737 | } |
| 1722 | 1738 | switch( tokenType ){ |
| 1723 | 1739 |
| --- src/wikiformat.c | |
| +++ src/wikiformat.c | |
| @@ -23,17 +23,16 @@ | |
| 23 | |
| 24 | #if INTERFACE |
| 25 | /* |
| 26 | ** Allowed wiki transformation operations |
| 27 | */ |
| 28 | #define WIKI_NOFOLLOW 0x001 |
| 29 | #define WIKI_HTML 0x002 |
| 30 | #define WIKI_INLINE 0x004 /* Do not surround with <p>..</p> */ |
| 31 | #define WIKI_NOBLOCK 0x008 /* No block markup of any kind */ |
| 32 | #define WIKI_BUTTONS 0x010 /* Allow sub-menu buttons */ |
| 33 | #define WIKI_NOBADLINKS 0x020 /* Ignore broken hyperlinks */ |
| 34 | #define WIKI_DECORATEONLY 0x040 /* No markup. Only decorate links */ |
| 35 | #endif |
| 36 | |
| 37 | |
| 38 | /* |
| 39 | ** These are the only markup attributes allowed. |
| @@ -389,11 +388,10 @@ | |
| 389 | #define AT_PARAGRAPH 0x0020000 /* At start of a paragraph */ |
| 390 | #define ALLOW_WIKI 0x0040000 /* Allow wiki markup */ |
| 391 | #define FONT_MARKUP_ONLY 0x0080000 /* Only allow MUTYPE_FONT markup */ |
| 392 | #define INLINE_MARKUP_ONLY 0x0100000 /* Allow only "inline" markup */ |
| 393 | #define IN_LIST 0x0200000 /* Within wiki <ul> or <ol> */ |
| 394 | #define WIKI_USE_HTML 0x0400000 /* wiki-use-html option = on */ |
| 395 | |
| 396 | /* |
| 397 | ** Current state of the rendering engine |
| 398 | */ |
| 399 | typedef struct Renderer Renderer; |
| @@ -985,10 +983,11 @@ | |
| 985 | /* |
| 986 | ** Begin a new paragraph if that something that is needed. |
| 987 | */ |
| 988 | static void startAutoParagraph(Renderer *p){ |
| 989 | if( p->wantAutoParagraph==0 ) return; |
| 990 | if( p->wikiList==MARKUP_OL || p->wikiList==MARKUP_UL ) return; |
| 991 | blob_appendf(p->pOut, "<p>", -1); |
| 992 | pushStack(p, MARKUP_P); |
| 993 | p->wantAutoParagraph = 0; |
| 994 | p->inAutoParagraph = 1; |
| @@ -1147,19 +1146,14 @@ | |
| 1147 | || strncmp(zTarget, "https:", 6)==0 |
| 1148 | || strncmp(zTarget, "ftp:", 4)==0 |
| 1149 | || strncmp(zTarget, "mailto:", 7)==0 |
| 1150 | ){ |
| 1151 | blob_appendf(p->pOut, "<a href=\"%s\">", zTarget); |
| 1152 | /* zTerm = "⟾</a>"; // doesn't work on windows */ |
| 1153 | }else if( zTarget[0]=='/' ){ |
| 1154 | blob_appendf(p->pOut, "<a href=\"%s%h\">", g.zTop, zTarget); |
| 1155 | }else if( zTarget[0]=='.' || zTarget[0]=='#' ){ |
| 1156 | if( 1 ){ |
| 1157 | blob_appendf(p->pOut, "<a href=\"%h\">", zTarget); |
| 1158 | }else{ |
| 1159 | zTerm = ""; |
| 1160 | } |
| 1161 | }else if( is_valid_uuid(zTarget) ){ |
| 1162 | int isClosed = 0; |
| 1163 | if( is_ticket(zTarget, &isClosed) ){ |
| 1164 | /* Special display processing for tickets. Display the hyperlink |
| 1165 | ** as crossed out if the ticket is closed. |
| @@ -1183,11 +1177,11 @@ | |
| 1183 | blob_appendf(p->pOut, "["); |
| 1184 | zTerm = "]"; |
| 1185 | } |
| 1186 | } |
| 1187 | }else if( !in_this_repo(zTarget) ){ |
| 1188 | if( (p->state & (WIKI_DECORATEONLY|WIKI_NOBADLINKS))!=0 ){ |
| 1189 | zTerm = ""; |
| 1190 | }else{ |
| 1191 | blob_appendf(p->pOut, "<span class=\"brokenlink\">[", zTarget); |
| 1192 | zTerm = "]</span>"; |
| 1193 | } |
| @@ -1201,11 +1195,11 @@ | |
| 1201 | }else if( (z = validWikiPageName(p, zTarget))!=0 ){ |
| 1202 | blob_appendf(p->pOut, "<a href=\"%R/wiki?name=%T\">", z); |
| 1203 | }else if( zTarget>=&zOrig[2] && !fossil_isspace(zTarget[-2]) ){ |
| 1204 | /* Probably an array subscript in code */ |
| 1205 | zTerm = ""; |
| 1206 | }else if( (p->state & (WIKI_NOBADLINKS|WIKI_DECORATEONLY))!=0 ){ |
| 1207 | zTerm = ""; |
| 1208 | }else{ |
| 1209 | blob_appendf(p->pOut, "<span class=\"brokenlink\">[%h]", zTarget); |
| 1210 | zTerm = "</span>"; |
| 1211 | } |
| @@ -1245,19 +1239,20 @@ | |
| 1245 | static void wiki_render(Renderer *p, char *z){ |
| 1246 | int tokenType; |
| 1247 | ParsedMarkup markup; |
| 1248 | int n; |
| 1249 | int inlineOnly = (p->state & INLINE_MARKUP_ONLY)!=0; |
| 1250 | int wikiUseHtml = (p->state & WIKI_USE_HTML)!=0; |
| 1251 | char *zOrig = z; |
| 1252 | |
| 1253 | /* Make sure the attribute constants and names still align |
| 1254 | ** following changes in the attribute list. */ |
| 1255 | assert( fossil_strcmp(aAttribute[ATTR_WIDTH].zName, "width")==0 ); |
| 1256 | |
| 1257 | while( z[0] ){ |
| 1258 | if( wikiUseHtml ){ |
| 1259 | n = nextRawToken(z, p, &tokenType); |
| 1260 | }else{ |
| 1261 | n = nextWikiToken(z, p, &tokenType); |
| 1262 | } |
| 1263 | p->state &= ~(AT_NEWLINE|AT_PARAGRAPH); |
| @@ -1374,33 +1369,37 @@ | |
| 1374 | startAutoParagraph(p); |
| 1375 | zTarget = &z[1]; |
| 1376 | for(i=1; z[i] && z[i]!=']'; i++){ |
| 1377 | if( z[i]=='|' && zDisplay==0 ){ |
| 1378 | zDisplay = &z[i+1]; |
| 1379 | iS1 = i; |
| 1380 | cS1 = z[i]; |
| 1381 | z[i] = 0; |
| 1382 | for(j=i-1; j>0 && fossil_isspace(z[j]); j--){ z[j] = 0; } |
| 1383 | } |
| 1384 | } |
| 1385 | z[i] = 0; |
| 1386 | if( zDisplay==0 ){ |
| 1387 | zDisplay = zTarget; |
| 1388 | }else{ |
| 1389 | while( fossil_isspace(*zDisplay) ) zDisplay++; |
| 1390 | } |
| 1391 | openHyperlink(p, zTarget, zClose, sizeof(zClose), zOrig); |
| 1392 | if( zClose[0] ){ |
| 1393 | savedState = p->state; |
| 1394 | p->state &= ~ALLOW_WIKI; |
| 1395 | p->state |= FONT_MARKUP_ONLY; |
| 1396 | wiki_render(p, zDisplay); |
| 1397 | p->state = savedState; |
| 1398 | blob_append(p->pOut, zClose, -1); |
| 1399 | }else{ |
| 1400 | if( cS1 ) z[iS1] = cS1; |
| 1401 | blob_appendf(p->pOut, "[%h]", zTarget); |
| 1402 | } |
| 1403 | break; |
| 1404 | } |
| 1405 | case TOKEN_TEXT: { |
| 1406 | int i; |
| @@ -1408,11 +1407,15 @@ | |
| 1408 | if( i<n ) startAutoParagraph(p); |
| 1409 | blob_append(p->pOut, z, n); |
| 1410 | break; |
| 1411 | } |
| 1412 | case TOKEN_RAW: { |
| 1413 | blob_append(p->pOut, z, n); |
| 1414 | break; |
| 1415 | } |
| 1416 | case TOKEN_MARKUP: { |
| 1417 | const char *zId; |
| 1418 | int iDiv; |
| @@ -1606,24 +1609,26 @@ | |
| 1606 | renderer.wantAutoParagraph = 0; |
| 1607 | }else{ |
| 1608 | renderer.wantAutoParagraph = 1; |
| 1609 | } |
| 1610 | if( wikiUsesHtml() ){ |
| 1611 | renderer.state |= WIKI_USE_HTML; |
| 1612 | } |
| 1613 | if( pOut ){ |
| 1614 | renderer.pOut = pOut; |
| 1615 | }else{ |
| 1616 | renderer.pOut = cgi_output_blob(); |
| 1617 | } |
| 1618 | |
| 1619 | blob_strip_bom(pIn, 0); |
| 1620 | wiki_render(&renderer, blob_str(pIn)); |
| 1621 | endAutoParagraph(&renderer); |
| 1622 | while( renderer.nStack ){ |
| 1623 | popStack(&renderer); |
| 1624 | } |
| 1625 | blob_append(renderer.pOut, "\n", 1); |
| 1626 | free(renderer.aStack); |
| 1627 | } |
| 1628 | |
| 1629 | /* |
| @@ -1631,15 +1636,26 @@ | |
| 1631 | ** |
| 1632 | ** %fossil test-wiki-render FILE [OPTIONS] |
| 1633 | ** |
| 1634 | ** Options: |
| 1635 | ** --buttons Set the WIKI_BUTTONS flag |
| 1636 | */ |
| 1637 | void test_wiki_render(void){ |
| 1638 | Blob in, out; |
| 1639 | int flags = 0; |
| 1640 | if( find_option("buttons",0,0)!=0 ) flags |= WIKI_BUTTONS; |
| 1641 | verify_all_options(); |
| 1642 | if( g.argc!=3 ) usage("FILE"); |
| 1643 | blob_zero(&out); |
| 1644 | blob_read_from_file(&in, g.argv[2]); |
| 1645 | wiki_convert(&in, &out, flags); |
| @@ -1694,29 +1710,29 @@ | |
| 1694 | Renderer renderer; |
| 1695 | int tokenType; |
| 1696 | ParsedMarkup markup; |
| 1697 | int n; |
| 1698 | int inlineOnly; |
| 1699 | int wikiUseHtml = 0; |
| 1700 | |
| 1701 | memset(&renderer, 0, sizeof(renderer)); |
| 1702 | renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH; |
| 1703 | if( flags & WIKI_NOBLOCK ){ |
| 1704 | renderer.state |= INLINE_MARKUP_ONLY; |
| 1705 | } |
| 1706 | if( wikiUsesHtml() ){ |
| 1707 | renderer.state |= WIKI_USE_HTML; |
| 1708 | wikiUseHtml = 1; |
| 1709 | } |
| 1710 | inlineOnly = (renderer.state & INLINE_MARKUP_ONLY)!=0; |
| 1711 | if( replaceFlag ){ |
| 1712 | db_multi_exec("DELETE FROM backlink WHERE srctype=%d AND srcid=%d", |
| 1713 | srctype, srcid); |
| 1714 | } |
| 1715 | |
| 1716 | while( z[0] ){ |
| 1717 | if( wikiUseHtml ){ |
| 1718 | n = nextRawToken(z, &renderer, &tokenType); |
| 1719 | }else{ |
| 1720 | n = nextWikiToken(z, &renderer, &tokenType); |
| 1721 | } |
| 1722 | switch( tokenType ){ |
| 1723 |
| --- src/wikiformat.c | |
| +++ src/wikiformat.c | |
| @@ -23,17 +23,16 @@ | |
| 23 | |
| 24 | #if INTERFACE |
| 25 | /* |
| 26 | ** Allowed wiki transformation operations |
| 27 | */ |
| 28 | #define WIKI_HTMLONLY 0x001 /* HTML markup only. No wiki */ |
| 29 | #define WIKI_INLINE 0x002 /* Do not surround with <p>..</p> */ |
| 30 | #define WIKI_NOBLOCK 0x004 /* No block markup of any kind */ |
| 31 | #define WIKI_BUTTONS 0x008 /* Allow sub-menu buttons */ |
| 32 | #define WIKI_NOBADLINKS 0x010 /* Ignore broken hyperlinks */ |
| 33 | #define WIKI_LINKSONLY 0x020 /* No markup. Only decorate links */ |
| 34 | #endif |
| 35 | |
| 36 | |
| 37 | /* |
| 38 | ** These are the only markup attributes allowed. |
| @@ -389,11 +388,10 @@ | |
| 388 | #define AT_PARAGRAPH 0x0020000 /* At start of a paragraph */ |
| 389 | #define ALLOW_WIKI 0x0040000 /* Allow wiki markup */ |
| 390 | #define FONT_MARKUP_ONLY 0x0080000 /* Only allow MUTYPE_FONT markup */ |
| 391 | #define INLINE_MARKUP_ONLY 0x0100000 /* Allow only "inline" markup */ |
| 392 | #define IN_LIST 0x0200000 /* Within wiki <ul> or <ol> */ |
| 393 | |
| 394 | /* |
| 395 | ** Current state of the rendering engine |
| 396 | */ |
| 397 | typedef struct Renderer Renderer; |
| @@ -985,10 +983,11 @@ | |
| 983 | /* |
| 984 | ** Begin a new paragraph if that something that is needed. |
| 985 | */ |
| 986 | static void startAutoParagraph(Renderer *p){ |
| 987 | if( p->wantAutoParagraph==0 ) return; |
| 988 | if( p->state & WIKI_LINKSONLY ) return; |
| 989 | if( p->wikiList==MARKUP_OL || p->wikiList==MARKUP_UL ) return; |
| 990 | blob_appendf(p->pOut, "<p>", -1); |
| 991 | pushStack(p, MARKUP_P); |
| 992 | p->wantAutoParagraph = 0; |
| 993 | p->inAutoParagraph = 1; |
| @@ -1147,19 +1146,14 @@ | |
| 1146 | || strncmp(zTarget, "https:", 6)==0 |
| 1147 | || strncmp(zTarget, "ftp:", 4)==0 |
| 1148 | || strncmp(zTarget, "mailto:", 7)==0 |
| 1149 | ){ |
| 1150 | blob_appendf(p->pOut, "<a href=\"%s\">", zTarget); |
| 1151 | }else if( zTarget[0]=='/' ){ |
| 1152 | blob_appendf(p->pOut, "<a href=\"%s%h\">", g.zTop, zTarget); |
| 1153 | }else if( zTarget[0]=='.' || zTarget[0]=='#' ){ |
| 1154 | blob_appendf(p->pOut, "<a href=\"%h\">", zTarget); |
| 1155 | }else if( is_valid_uuid(zTarget) ){ |
| 1156 | int isClosed = 0; |
| 1157 | if( is_ticket(zTarget, &isClosed) ){ |
| 1158 | /* Special display processing for tickets. Display the hyperlink |
| 1159 | ** as crossed out if the ticket is closed. |
| @@ -1183,11 +1177,11 @@ | |
| 1177 | blob_appendf(p->pOut, "["); |
| 1178 | zTerm = "]"; |
| 1179 | } |
| 1180 | } |
| 1181 | }else if( !in_this_repo(zTarget) ){ |
| 1182 | if( (p->state & (WIKI_LINKSONLY|WIKI_NOBADLINKS))!=0 ){ |
| 1183 | zTerm = ""; |
| 1184 | }else{ |
| 1185 | blob_appendf(p->pOut, "<span class=\"brokenlink\">[", zTarget); |
| 1186 | zTerm = "]</span>"; |
| 1187 | } |
| @@ -1201,11 +1195,11 @@ | |
| 1195 | }else if( (z = validWikiPageName(p, zTarget))!=0 ){ |
| 1196 | blob_appendf(p->pOut, "<a href=\"%R/wiki?name=%T\">", z); |
| 1197 | }else if( zTarget>=&zOrig[2] && !fossil_isspace(zTarget[-2]) ){ |
| 1198 | /* Probably an array subscript in code */ |
| 1199 | zTerm = ""; |
| 1200 | }else if( (p->state & (WIKI_NOBADLINKS|WIKI_LINKSONLY))!=0 ){ |
| 1201 | zTerm = ""; |
| 1202 | }else{ |
| 1203 | blob_appendf(p->pOut, "<span class=\"brokenlink\">[%h]", zTarget); |
| 1204 | zTerm = "</span>"; |
| 1205 | } |
| @@ -1245,19 +1239,20 @@ | |
| 1239 | static void wiki_render(Renderer *p, char *z){ |
| 1240 | int tokenType; |
| 1241 | ParsedMarkup markup; |
| 1242 | int n; |
| 1243 | int inlineOnly = (p->state & INLINE_MARKUP_ONLY)!=0; |
| 1244 | int wikiHtmlOnly = (p->state & (WIKI_HTMLONLY | WIKI_LINKSONLY))!=0; |
| 1245 | int linksOnly = (p->state & WIKI_LINKSONLY)!=0; |
| 1246 | char *zOrig = z; |
| 1247 | |
| 1248 | /* Make sure the attribute constants and names still align |
| 1249 | ** following changes in the attribute list. */ |
| 1250 | assert( fossil_strcmp(aAttribute[ATTR_WIDTH].zName, "width")==0 ); |
| 1251 | |
| 1252 | while( z[0] ){ |
| 1253 | if( wikiHtmlOnly ){ |
| 1254 | n = nextRawToken(z, p, &tokenType); |
| 1255 | }else{ |
| 1256 | n = nextWikiToken(z, p, &tokenType); |
| 1257 | } |
| 1258 | p->state &= ~(AT_NEWLINE|AT_PARAGRAPH); |
| @@ -1374,33 +1369,37 @@ | |
| 1369 | startAutoParagraph(p); |
| 1370 | zTarget = &z[1]; |
| 1371 | for(i=1; z[i] && z[i]!=']'; i++){ |
| 1372 | if( z[i]=='|' && zDisplay==0 ){ |
| 1373 | zDisplay = &z[i+1]; |
| 1374 | for(j=i; j>0 && fossil_isspace(z[j-1]); j--){} |
| 1375 | iS1 = j; |
| 1376 | cS1 = z[j]; |
| 1377 | z[j] = 0; |
| 1378 | } |
| 1379 | } |
| 1380 | z[i] = 0; |
| 1381 | if( zDisplay==0 ){ |
| 1382 | zDisplay = zTarget; |
| 1383 | }else{ |
| 1384 | while( fossil_isspace(*zDisplay) ) zDisplay++; |
| 1385 | } |
| 1386 | openHyperlink(p, zTarget, zClose, sizeof(zClose), zOrig); |
| 1387 | if( linksOnly || zClose[0]==0 ){ |
| 1388 | if( cS1 ) z[iS1] = cS1; |
| 1389 | if( zClose[0]!=']' ){ |
| 1390 | blob_appendf(p->pOut, "[%h]%s", zTarget, zClose); |
| 1391 | }else{ |
| 1392 | blob_appendf(p->pOut, "%h%s", zTarget, zClose); |
| 1393 | } |
| 1394 | }else{ |
| 1395 | savedState = p->state; |
| 1396 | p->state &= ~ALLOW_WIKI; |
| 1397 | p->state |= FONT_MARKUP_ONLY; |
| 1398 | wiki_render(p, zDisplay); |
| 1399 | p->state = savedState; |
| 1400 | blob_append(p->pOut, zClose, -1); |
| 1401 | } |
| 1402 | break; |
| 1403 | } |
| 1404 | case TOKEN_TEXT: { |
| 1405 | int i; |
| @@ -1408,11 +1407,15 @@ | |
| 1407 | if( i<n ) startAutoParagraph(p); |
| 1408 | blob_append(p->pOut, z, n); |
| 1409 | break; |
| 1410 | } |
| 1411 | case TOKEN_RAW: { |
| 1412 | if( linksOnly ){ |
| 1413 | htmlize_to_blob(p->pOut, z, n); |
| 1414 | }else{ |
| 1415 | blob_append(p->pOut, z, n); |
| 1416 | } |
| 1417 | break; |
| 1418 | } |
| 1419 | case TOKEN_MARKUP: { |
| 1420 | const char *zId; |
| 1421 | int iDiv; |
| @@ -1606,24 +1609,26 @@ | |
| 1609 | renderer.wantAutoParagraph = 0; |
| 1610 | }else{ |
| 1611 | renderer.wantAutoParagraph = 1; |
| 1612 | } |
| 1613 | if( wikiUsesHtml() ){ |
| 1614 | renderer.state |= WIKI_HTMLONLY; |
| 1615 | } |
| 1616 | if( pOut ){ |
| 1617 | renderer.pOut = pOut; |
| 1618 | }else{ |
| 1619 | renderer.pOut = cgi_output_blob(); |
| 1620 | } |
| 1621 | |
| 1622 | blob_strip_bom(pIn, 0); |
| 1623 | if( flags & WIKI_LINKSONLY ) blob_append(renderer.pOut, "<pre>", 5); |
| 1624 | wiki_render(&renderer, blob_str(pIn)); |
| 1625 | endAutoParagraph(&renderer); |
| 1626 | while( renderer.nStack ){ |
| 1627 | popStack(&renderer); |
| 1628 | } |
| 1629 | if( flags & WIKI_LINKSONLY ) blob_append(renderer.pOut, "</pre>", 5); |
| 1630 | blob_append(renderer.pOut, "\n", 1); |
| 1631 | free(renderer.aStack); |
| 1632 | } |
| 1633 | |
| 1634 | /* |
| @@ -1631,15 +1636,26 @@ | |
| 1636 | ** |
| 1637 | ** %fossil test-wiki-render FILE [OPTIONS] |
| 1638 | ** |
| 1639 | ** Options: |
| 1640 | ** --buttons Set the WIKI_BUTTONS flag |
| 1641 | ** --htmlonly Set the WIKI_HTMLONLY flag |
| 1642 | ** --linksonly Set the WIKI_LINKSONLY flag |
| 1643 | ** --nobadlinks Set the WIKI_NOBADLINKS flag |
| 1644 | ** --inline Set the WIKI_INLINE flag |
| 1645 | ** --noblock Set the WIKI_NOBLOCK flag |
| 1646 | */ |
| 1647 | void test_wiki_render(void){ |
| 1648 | Blob in, out; |
| 1649 | int flags = 0; |
| 1650 | if( find_option("buttons",0,0)!=0 ) flags |= WIKI_BUTTONS; |
| 1651 | if( find_option("htmlonly",0,0)!=0 ) flags |= WIKI_HTMLONLY; |
| 1652 | if( find_option("linksonly",0,0)!=0 ) flags |= WIKI_LINKSONLY; |
| 1653 | if( find_option("nobadlinks",0,0)!=0 ) flags |= WIKI_NOBADLINKS; |
| 1654 | if( find_option("inline",0,0)!=0 ) flags |= WIKI_INLINE; |
| 1655 | if( find_option("noblock",0,0)!=0 ) flags |= WIKI_NOBLOCK; |
| 1656 | db_find_and_open_repository(0,0); |
| 1657 | verify_all_options(); |
| 1658 | if( g.argc!=3 ) usage("FILE"); |
| 1659 | blob_zero(&out); |
| 1660 | blob_read_from_file(&in, g.argv[2]); |
| 1661 | wiki_convert(&in, &out, flags); |
| @@ -1694,29 +1710,29 @@ | |
| 1710 | Renderer renderer; |
| 1711 | int tokenType; |
| 1712 | ParsedMarkup markup; |
| 1713 | int n; |
| 1714 | int inlineOnly; |
| 1715 | int wikiHtmlOnly = 0; |
| 1716 | |
| 1717 | memset(&renderer, 0, sizeof(renderer)); |
| 1718 | renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH; |
| 1719 | if( flags & WIKI_NOBLOCK ){ |
| 1720 | renderer.state |= INLINE_MARKUP_ONLY; |
| 1721 | } |
| 1722 | if( wikiUsesHtml() ){ |
| 1723 | renderer.state |= WIKI_HTMLONLY; |
| 1724 | wikiHtmlOnly = 1; |
| 1725 | } |
| 1726 | inlineOnly = (renderer.state & INLINE_MARKUP_ONLY)!=0; |
| 1727 | if( replaceFlag ){ |
| 1728 | db_multi_exec("DELETE FROM backlink WHERE srctype=%d AND srcid=%d", |
| 1729 | srctype, srcid); |
| 1730 | } |
| 1731 | |
| 1732 | while( z[0] ){ |
| 1733 | if( wikiHtmlOnly ){ |
| 1734 | n = nextRawToken(z, &renderer, &tokenType); |
| 1735 | }else{ |
| 1736 | n = nextWikiToken(z, &renderer, &tokenType); |
| 1737 | } |
| 1738 | switch( tokenType ){ |
| 1739 |