Fossil SCM
Begin implementing a return code on wiki_convert() that can inform the caller about what markup is contained within the rendered wiki, and what kinds of errors were seen.
Commit
53b080b24509ffaba5e65f20530829a5903b369bf745b46b5af0d6ff0f0bce33
Parent
bff93fc819b904d…
1 file changed
+107
-30
+107
-30
| --- src/wikiformat.c | ||
| +++ src/wikiformat.c | ||
| @@ -23,23 +23,41 @@ | ||
| 23 | 23 | |
| 24 | 24 | #if INTERFACE |
| 25 | 25 | /* |
| 26 | 26 | ** Allowed wiki transformation operations |
| 27 | 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 | -#define WIKI_NEWLINE 0x040 /* Honor \n - break lines at each \n */ | |
| 35 | -#define WIKI_MARKDOWNLINKS 0x080 /* Resolve hyperlinks as in markdown */ | |
| 36 | -#define WIKI_SAFE 0x100 /* Make the result safe for embedding */ | |
| 37 | -#define WIKI_TARGET_BLANK 0x200 /* Hyperlinks go to a new window */ | |
| 38 | -#define WIKI_NOBRACKET 0x400 /* Omit extra [..] around hyperlinks */ | |
| 39 | -#define WIKI_ADMIN 0x800 /* Ignore g.perm.Hyperlink */ | |
| 40 | -#endif | |
| 28 | +#define WIKI_HTMLONLY 0x0001 /* HTML markup only. No wiki */ | |
| 29 | +#define WIKI_INLINE 0x0002 /* Do not surround with <p>..</p> */ | |
| 30 | +#define WIKI_NOBLOCK 0x0004 /* No block markup of any kind */ | |
| 31 | +#define WIKI_BUTTONS 0x0008 /* Allow sub-menu buttons */ | |
| 32 | +#define WIKI_NOBADLINKS 0x0010 /* Ignore broken hyperlinks */ | |
| 33 | +#define WIKI_LINKSONLY 0x0020 /* No markup. Only decorate links */ | |
| 34 | +#define WIKI_NEWLINE 0x0040 /* Honor \n - break lines at each \n */ | |
| 35 | +#define WIKI_MARKDOWNLINKS 0x0080 /* Resolve hyperlinks as in markdown */ | |
| 36 | +#define WIKI_SAFE 0x0100 /* Make the result safe for embedding */ | |
| 37 | +#define WIKI_TARGET_BLANK 0x0200 /* Hyperlinks go to a new window */ | |
| 38 | +#define WIKI_NOBRACKET 0x0400 /* Omit extra [..] around hyperlinks */ | |
| 39 | +#define WIKI_ADMIN 0x0800 /* Ignore g.perm.Hyperlink */ | |
| 40 | + | |
| 41 | +/* | |
| 42 | +** Return values from wiki_convert | |
| 43 | +*/ | |
| 44 | +#define RENDER_LINK 0x0001 /* One or more hyperlinks rendered */ | |
| 45 | +#define RENDER_ENTITY 0x0002 /* One or more HTML entities (ex: <) */ | |
| 46 | +#define RENDER_TAG 0x0004 /* One or more HTML tags */ | |
| 47 | +#define RENDER_BLOCKTAG 0x0008 /* One or more HTML block tags (ex: <p>) */ | |
| 48 | +#define RENDER_BLOCK 0x0010 /* Block wiki (paragraphs, etc.) */ | |
| 49 | +#define RENDER_BADLINK 0x0100 /* Bad hyperlink syntax seen */ | |
| 50 | +#define RENDER_BADTARGET 0x0200 /* Bad hyperlink target */ | |
| 51 | +#define RENDER_BADTAG 0x0400 /* Bad HTML tag or tag syntax */ | |
| 52 | +#define RENDER_BADENTITY 0x0800 /* Bad HTML entity syntax */ | |
| 53 | +#define RENDER_BADHTML 0x1000 /* Bad HTML seen */ | |
| 54 | +#define RENDER_ERROR 0x8000 /* Some other kind of error */ | |
| 55 | +/* Composite values: */ | |
| 56 | +#define RENDER_ANYERROR 0x9f00 /* Mask for any kind of error */ | |
| 57 | + | |
| 58 | +#endif /* INTERFACE */ | |
| 41 | 59 | |
| 42 | 60 | |
| 43 | 61 | /* |
| 44 | 62 | ** These are the only markup attributes allowed. |
| 45 | 63 | */ |
| @@ -455,10 +473,11 @@ | ||
| 455 | 473 | */ |
| 456 | 474 | typedef struct Renderer Renderer; |
| 457 | 475 | struct Renderer { |
| 458 | 476 | Blob *pOut; /* Output appended to this blob */ |
| 459 | 477 | int state; /* Flag that govern rendering */ |
| 478 | + int mRender; /* Mask of RENDER_* values to return */ | |
| 460 | 479 | unsigned renderFlags; /* Flags from the client */ |
| 461 | 480 | int wikiList; /* Current wiki list type */ |
| 462 | 481 | int inVerbatim; /* True in <verbatim> mode */ |
| 463 | 482 | int preVerbState; /* Value of state prior to verbatim */ |
| 464 | 483 | int wantAutoParagraph; /* True if a <p> is desired */ |
| @@ -680,21 +699,26 @@ | ||
| 680 | 699 | static int nextWikiToken(const char *z, Renderer *p, int *pTokenType){ |
| 681 | 700 | int n; |
| 682 | 701 | if( z[0]=='<' ){ |
| 683 | 702 | n = html_tag_length(z); |
| 684 | 703 | if( n>0 ){ |
| 704 | + p->mRender |= RENDER_TAG; | |
| 685 | 705 | *pTokenType = TOKEN_MARKUP; |
| 686 | 706 | return n; |
| 687 | 707 | }else{ |
| 708 | + p->mRender |= RENDER_BADTAG; | |
| 709 | + *pTokenType = TOKEN_CHARACTER; | |
| 710 | + return 1; | |
| 711 | + } | |
| 712 | + } | |
| 713 | + if( z[0]=='&' ){ | |
| 714 | + p->mRender |= RENDER_ENTITY; | |
| 715 | + if( (p->inVerbatim || !isElement(z)) ){ | |
| 688 | 716 | *pTokenType = TOKEN_CHARACTER; |
| 689 | 717 | return 1; |
| 690 | 718 | } |
| 691 | 719 | } |
| 692 | - if( z[0]=='&' && (p->inVerbatim || !isElement(z)) ){ | |
| 693 | - *pTokenType = TOKEN_CHARACTER; | |
| 694 | - return 1; | |
| 695 | - } | |
| 696 | 720 | if( (p->state & ALLOW_WIKI)!=0 ){ |
| 697 | 721 | if( z[0]=='\n' ){ |
| 698 | 722 | n = paragraphBreakLength(z); |
| 699 | 723 | if( n>0 ){ |
| 700 | 724 | *pTokenType = TOKEN_PARAGRAPH; |
| @@ -726,17 +750,25 @@ | ||
| 726 | 750 | if( n>0 ){ |
| 727 | 751 | *pTokenType = TOKEN_INDENT; |
| 728 | 752 | return n; |
| 729 | 753 | } |
| 730 | 754 | } |
| 731 | - if( z[0]=='[' && (n = linkLength(z))>0 ){ | |
| 755 | + if( z[0]=='[' ){ | |
| 756 | + if( (n = linkLength(z))>0 ){ | |
| 757 | + *pTokenType = TOKEN_LINK; | |
| 758 | + return n; | |
| 759 | + }else{ | |
| 760 | + p->mRender |= RENDER_BADLINK; | |
| 761 | + } | |
| 762 | + } | |
| 763 | + }else if( (p->state & ALLOW_LINKS)!=0 && z[0]=='[' ){ | |
| 764 | + if( (n = linkLength(z))>0 ){ | |
| 732 | 765 | *pTokenType = TOKEN_LINK; |
| 733 | 766 | return n; |
| 767 | + }else{ | |
| 768 | + p->mRender |= RENDER_BADLINK; | |
| 734 | 769 | } |
| 735 | - }else if( (p->state & ALLOW_LINKS)!=0 && z[0]=='[' && (n = linkLength(z))>0 ){ | |
| 736 | - *pTokenType = TOKEN_LINK; | |
| 737 | - return n; | |
| 738 | 770 | } |
| 739 | 771 | *pTokenType = TOKEN_TEXT; |
| 740 | 772 | return 1 + textLength(z+1, p->state); |
| 741 | 773 | } |
| 742 | 774 | |
| @@ -746,13 +778,17 @@ | ||
| 746 | 778 | ** z points to the start of a token. Return the number of |
| 747 | 779 | ** characters in that token. Write the token type into *pTokenType. |
| 748 | 780 | */ |
| 749 | 781 | static int nextRawToken(const char *z, Renderer *p, int *pTokenType){ |
| 750 | 782 | int n; |
| 751 | - if( z[0]=='[' && (n = linkLength(z))>0 ){ | |
| 752 | - *pTokenType = TOKEN_LINK; | |
| 753 | - return n; | |
| 783 | + if( z[0]=='[' ){ | |
| 784 | + if( (n = linkLength(z))>0 ){ | |
| 785 | + *pTokenType = TOKEN_LINK; | |
| 786 | + return n; | |
| 787 | + }else{ | |
| 788 | + p->mRender |= RENDER_BADLINK; | |
| 789 | + } | |
| 754 | 790 | } |
| 755 | 791 | *pTokenType = TOKEN_RAW; |
| 756 | 792 | return 1 + textLength(z+1, p->state); |
| 757 | 793 | } |
| 758 | 794 | |
| @@ -1249,12 +1285,16 @@ | ||
| 1249 | 1285 | ** [wiki:WikiPageName] |
| 1250 | 1286 | ** |
| 1251 | 1287 | ** [2010-02-27 07:13] |
| 1252 | 1288 | ** |
| 1253 | 1289 | ** [InterMap:Link] -> Interwiki link |
| 1290 | +** | |
| 1291 | +** The return value is a mask of RENDER_* values indicating what happened. | |
| 1292 | +** Probably the return value is 0 on success and RENDER_BADTARGET or | |
| 1293 | +** RENDER_BADLINK if there are problems. | |
| 1254 | 1294 | */ |
| 1255 | -void wiki_resolve_hyperlink( | |
| 1295 | +int wiki_resolve_hyperlink( | |
| 1256 | 1296 | Blob *pOut, /* Write the HTML output here */ |
| 1257 | 1297 | int mFlags, /* Rendering option flags */ |
| 1258 | 1298 | const char *zTarget, /* Hyperlink target; text within [...] */ |
| 1259 | 1299 | char *zClose, /* Write hyperlink closing text here */ |
| 1260 | 1300 | int nClose, /* Bytes available in zClose[] */ |
| @@ -1264,10 +1304,11 @@ | ||
| 1264 | 1304 | const char *zTerm = "</a>"; |
| 1265 | 1305 | const char *z; |
| 1266 | 1306 | char *zExtra = 0; |
| 1267 | 1307 | const char *zExtraNS = 0; |
| 1268 | 1308 | char *zRemote = 0; |
| 1309 | + int rc = 0; | |
| 1269 | 1310 | |
| 1270 | 1311 | if( zTitle ){ |
| 1271 | 1312 | zExtra = mprintf(" title='%h'", zTitle); |
| 1272 | 1313 | zExtraNS = zExtra+1; |
| 1273 | 1314 | }else if( mFlags & WIKI_TARGET_BLANK ){ |
| @@ -1321,10 +1362,11 @@ | ||
| 1321 | 1362 | zTerm = ""; |
| 1322 | 1363 | }else{ |
| 1323 | 1364 | blob_appendf(pOut, "<span class=\"brokenlink\">%s", zLB); |
| 1324 | 1365 | zTerm = "]</span>"; |
| 1325 | 1366 | } |
| 1367 | + rc |= RENDER_BADTARGET; | |
| 1326 | 1368 | }else if( g.perm.Hyperlink || (mFlags & WIKI_ADMIN)!=0 ){ |
| 1327 | 1369 | blob_appendf(pOut, "%z%s",xhref(zExtraNS, "%R/info/%s", zTarget), zLB); |
| 1328 | 1370 | zTerm = "]</a>"; |
| 1329 | 1371 | }else{ |
| 1330 | 1372 | zTerm = ""; |
| @@ -1341,11 +1383,11 @@ | ||
| 1341 | 1383 | }else{ |
| 1342 | 1384 | blob_appendf(pOut, "<a href=\"%R/wiki?name=%T\"%s>", z, zExtra); |
| 1343 | 1385 | } |
| 1344 | 1386 | }else if( strlen(zTarget)>=10 && fossil_isdigit(zTarget[0]) && zTarget[4]=='-' |
| 1345 | 1387 | && db_int(0, "SELECT datetime(%Q) NOT NULL", zTarget) ){ |
| 1346 | - /* Dates or date-and-times in ISO8610 resolve to a link to the | |
| 1388 | + /* Dates or date-and-times in ISO8601 resolve to a link to the | |
| 1347 | 1389 | ** timeline for that date */ |
| 1348 | 1390 | blob_appendf(pOut, "<a href=\"%R/timeline?c=%T\"%s>", zTarget, zExtra); |
| 1349 | 1391 | }else if( mFlags & WIKI_MARKDOWNLINKS ){ |
| 1350 | 1392 | /* If none of the above, and if rendering links for markdown, then |
| 1351 | 1393 | ** create a link to the literal text of the target */ |
| @@ -1357,17 +1399,20 @@ | ||
| 1357 | 1399 | ** hyperlink. Just ignore it. */ |
| 1358 | 1400 | zTerm = ""; |
| 1359 | 1401 | }else if( (mFlags & (WIKI_NOBADLINKS|WIKI_LINKSONLY))!=0 ){ |
| 1360 | 1402 | /* Also ignore the link if various flags are set */ |
| 1361 | 1403 | zTerm = ""; |
| 1404 | + rc |= RENDER_BADTARGET; | |
| 1362 | 1405 | }else{ |
| 1363 | 1406 | blob_appendf(pOut, "<span class=\"brokenlink\">[%h]", zTarget); |
| 1364 | 1407 | zTerm = "</span>"; |
| 1408 | + rc |= RENDER_BADTARGET; | |
| 1365 | 1409 | } |
| 1366 | 1410 | if( zExtra ) fossil_free(zExtra); |
| 1367 | 1411 | assert( (int)strlen(zTerm)<nClose ); |
| 1368 | 1412 | sqlite3_snprintf(nClose, zClose, "%s", zTerm); |
| 1413 | + return rc; | |
| 1369 | 1414 | } |
| 1370 | 1415 | |
| 1371 | 1416 | /* |
| 1372 | 1417 | ** Check zTarget to see if it looks like a valid hyperlink target. |
| 1373 | 1418 | ** Return true if it does seem valid and false if not. |
| @@ -1508,10 +1553,11 @@ | ||
| 1508 | 1553 | } |
| 1509 | 1554 | p->state |= AT_NEWLINE; |
| 1510 | 1555 | break; |
| 1511 | 1556 | } |
| 1512 | 1557 | case TOKEN_BUL_LI: { |
| 1558 | + p->mRender |= RENDER_BLOCK; | |
| 1513 | 1559 | if( inlineOnly ){ |
| 1514 | 1560 | blob_append_string(p->pOut, " • "); |
| 1515 | 1561 | }else{ |
| 1516 | 1562 | if( p->wikiList!=MARKUP_UL ){ |
| 1517 | 1563 | if( p->wikiList ){ |
| @@ -1528,10 +1574,11 @@ | ||
| 1528 | 1574 | blob_append_string(p->pOut, "<li>"); |
| 1529 | 1575 | } |
| 1530 | 1576 | break; |
| 1531 | 1577 | } |
| 1532 | 1578 | case TOKEN_NUM_LI: { |
| 1579 | + p->mRender |= RENDER_BLOCK; | |
| 1533 | 1580 | if( inlineOnly ){ |
| 1534 | 1581 | blob_append_string(p->pOut, " # "); |
| 1535 | 1582 | }else{ |
| 1536 | 1583 | if( p->wikiList!=MARKUP_OL ){ |
| 1537 | 1584 | if( p->wikiList ){ |
| @@ -1548,10 +1595,11 @@ | ||
| 1548 | 1595 | blob_append_string(p->pOut, "<li>"); |
| 1549 | 1596 | } |
| 1550 | 1597 | break; |
| 1551 | 1598 | } |
| 1552 | 1599 | case TOKEN_ENUM: { |
| 1600 | + p->mRender |= RENDER_BLOCK; | |
| 1553 | 1601 | if( inlineOnly ){ |
| 1554 | 1602 | blob_appendf(p->pOut, " (%d) ", atoi(z)); |
| 1555 | 1603 | }else{ |
| 1556 | 1604 | if( p->wikiList!=MARKUP_OL ){ |
| 1557 | 1605 | if( p->wikiList ){ |
| @@ -1568,10 +1616,11 @@ | ||
| 1568 | 1616 | blob_appendf(p->pOut, "<li value=\"%d\">", atoi(z)); |
| 1569 | 1617 | } |
| 1570 | 1618 | break; |
| 1571 | 1619 | } |
| 1572 | 1620 | case TOKEN_INDENT: { |
| 1621 | + p->mRender |= RENDER_BLOCK; | |
| 1573 | 1622 | if( !inlineOnly ){ |
| 1574 | 1623 | assert( p->wikiList==0 ); |
| 1575 | 1624 | pushStack(p, MARKUP_BLOCKQUOTE); |
| 1576 | 1625 | blob_append_string(p->pOut, "<blockquote>"); |
| 1577 | 1626 | p->wantAutoParagraph = 0; |
| @@ -1596,10 +1645,11 @@ | ||
| 1596 | 1645 | char zClose[20]; |
| 1597 | 1646 | char cS1 = 0; |
| 1598 | 1647 | int iS1 = 0; |
| 1599 | 1648 | |
| 1600 | 1649 | startAutoParagraph(p); |
| 1650 | + p->mRender |= RENDER_LINK; | |
| 1601 | 1651 | zTarget = &z[1]; |
| 1602 | 1652 | for(i=1; z[i] && z[i]!=']'; i++){ |
| 1603 | 1653 | if( z[i]=='|' && zDisplay==0 ){ |
| 1604 | 1654 | zDisplay = &z[i+1]; |
| 1605 | 1655 | for(j=i; j>0 && fossil_isspace(z[j-1]); j--){} |
| @@ -1612,11 +1662,11 @@ | ||
| 1612 | 1662 | if( zDisplay==0 ){ |
| 1613 | 1663 | zDisplay = zTarget + interwiki_removable_prefix(zTarget); |
| 1614 | 1664 | }else{ |
| 1615 | 1665 | while( fossil_isspace(*zDisplay) ) zDisplay++; |
| 1616 | 1666 | } |
| 1617 | - wiki_resolve_hyperlink(p->pOut, p->state, | |
| 1667 | + p->mRender |= wiki_resolve_hyperlink(p->pOut, p->state, | |
| 1618 | 1668 | zTarget, zClose, sizeof(zClose), zOrig, 0); |
| 1619 | 1669 | if( linksOnly || zClose[0]==0 || p->inVerbatim ){ |
| 1620 | 1670 | if( cS1 ) z[iS1] = cS1; |
| 1621 | 1671 | if( zClose[0]!=']' ){ |
| 1622 | 1672 | blob_appendf(p->pOut, "[%h]%s", zTarget, zClose); |
| @@ -1702,10 +1752,11 @@ | ||
| 1702 | 1752 | |
| 1703 | 1753 | /* Render invalid markup literally. The markup appears in the |
| 1704 | 1754 | ** final output as plain text. |
| 1705 | 1755 | */ |
| 1706 | 1756 | if( markup.iCode==MARKUP_INVALID ){ |
| 1757 | + p->mRender |= RENDER_BADTAG; | |
| 1707 | 1758 | unparseMarkup(&markup); |
| 1708 | 1759 | startAutoParagraph(p); |
| 1709 | 1760 | blob_append_string(p->pOut, "<"); |
| 1710 | 1761 | n = 1; |
| 1711 | 1762 | }else |
| @@ -1814,10 +1865,11 @@ | ||
| 1814 | 1865 | }else |
| 1815 | 1866 | { |
| 1816 | 1867 | if( markup.iType==MUTYPE_FONT ){ |
| 1817 | 1868 | startAutoParagraph(p); |
| 1818 | 1869 | }else if( markup.iType==MUTYPE_BLOCK || markup.iType==MUTYPE_LIST ){ |
| 1870 | + p->mRender |= RENDER_BLOCKTAG; | |
| 1819 | 1871 | p->wantAutoParagraph = 0; |
| 1820 | 1872 | } |
| 1821 | 1873 | if( markup.iCode==MARKUP_HR |
| 1822 | 1874 | || markup.iCode==MARKUP_H1 |
| 1823 | 1875 | || markup.iCode==MARKUP_H2 |
| @@ -1844,12 +1896,14 @@ | ||
| 1844 | 1896 | ** Transform the text in the pIn blob. Write the results |
| 1845 | 1897 | ** into the pOut blob. The pOut blob should already be |
| 1846 | 1898 | ** initialized. The output is merely appended to pOut. |
| 1847 | 1899 | ** If pOut is NULL, then the output is appended to the CGI |
| 1848 | 1900 | ** reply. |
| 1901 | +** | |
| 1902 | +** Return a mask of RENDER_ flags indicating what happened. | |
| 1849 | 1903 | */ |
| 1850 | -void wiki_convert(Blob *pIn, Blob *pOut, int flags){ | |
| 1904 | +int wiki_convert(Blob *pIn, Blob *pOut, int flags){ | |
| 1851 | 1905 | Renderer renderer; |
| 1852 | 1906 | |
| 1853 | 1907 | memset(&renderer, 0, sizeof(renderer)); |
| 1854 | 1908 | renderer.renderFlags = flags; |
| 1855 | 1909 | renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH|flags; |
| @@ -1873,10 +1927,11 @@ | ||
| 1873 | 1927 | while( renderer.nStack ){ |
| 1874 | 1928 | popStack(&renderer); |
| 1875 | 1929 | } |
| 1876 | 1930 | blob_append_char(renderer.pOut, '\n'); |
| 1877 | 1931 | free(renderer.aStack); |
| 1932 | + return renderer.mRender; | |
| 1878 | 1933 | } |
| 1879 | 1934 | |
| 1880 | 1935 | /* |
| 1881 | 1936 | ** COMMAND: test-wiki-render |
| 1882 | 1937 | ** |
| @@ -1892,15 +1947,18 @@ | ||
| 1892 | 1947 | ** --inline Set the WIKI_INLINE flag |
| 1893 | 1948 | ** --linksonly Set the WIKI_LINKSONLY flag |
| 1894 | 1949 | ** --nobadlinks Set the WIKI_NOBADLINKS flag |
| 1895 | 1950 | ** --noblock Set the WIKI_NOBLOCK flag |
| 1896 | 1951 | ** --text Run the output through html_to_plaintext(). |
| 1952 | +** --type Break down the return code from wiki_convert(). | |
| 1897 | 1953 | */ |
| 1898 | 1954 | void test_wiki_render(void){ |
| 1899 | 1955 | Blob in, out; |
| 1900 | 1956 | int flags = 0; |
| 1901 | 1957 | int bText; |
| 1958 | + int showType = 0; | |
| 1959 | + int mType; | |
| 1902 | 1960 | if( find_option("buttons",0,0)!=0 ) flags |= WIKI_BUTTONS; |
| 1903 | 1961 | if( find_option("htmlonly",0,0)!=0 ) flags |= WIKI_HTMLONLY; |
| 1904 | 1962 | if( find_option("linksonly",0,0)!=0 ) flags |= WIKI_LINKSONLY; |
| 1905 | 1963 | if( find_option("nobadlinks",0,0)!=0 ) flags |= WIKI_NOBADLINKS; |
| 1906 | 1964 | if( find_option("inline",0,0)!=0 ) flags |= WIKI_INLINE; |
| @@ -1907,24 +1965,40 @@ | ||
| 1907 | 1965 | if( find_option("noblock",0,0)!=0 ) flags |= WIKI_NOBLOCK; |
| 1908 | 1966 | if( find_option("dark-pikchr",0,0)!=0 ){ |
| 1909 | 1967 | pikchr_to_html_add_flags( PIKCHR_PROCESS_DARK_MODE ); |
| 1910 | 1968 | } |
| 1911 | 1969 | bText = find_option("text",0,0)!=0; |
| 1970 | + showType = find_option("type",0,0)!=0; | |
| 1912 | 1971 | db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0); |
| 1913 | 1972 | verify_all_options(); |
| 1914 | 1973 | if( g.argc!=3 ) usage("FILE"); |
| 1915 | 1974 | blob_zero(&out); |
| 1916 | 1975 | blob_read_from_file(&in, g.argv[2], ExtFILE); |
| 1917 | - wiki_convert(&in, &out, flags); | |
| 1976 | + mType = wiki_convert(&in, &out, flags); | |
| 1918 | 1977 | if( bText ){ |
| 1919 | 1978 | Blob txt; |
| 1920 | 1979 | blob_init(&txt, 0, 0); |
| 1921 | 1980 | html_to_plaintext(blob_str(&out),&txt); |
| 1922 | 1981 | blob_reset(&out); |
| 1923 | 1982 | out = txt; |
| 1924 | 1983 | } |
| 1925 | 1984 | blob_write_to_file(&out, "-"); |
| 1985 | + if( showType ){ | |
| 1986 | + fossil_print("%.*c\nResult Codes:", terminal_get_width(80)-1, '*'); | |
| 1987 | + if( mType & RENDER_LINK ) fossil_print(" LINK"); | |
| 1988 | + if( mType & RENDER_ENTITY ) fossil_print(" ENTITY"); | |
| 1989 | + if( mType & RENDER_TAG ) fossil_print(" TAG"); | |
| 1990 | + if( mType & RENDER_BLOCKTAG ) fossil_print(" BLOCKTAG"); | |
| 1991 | + if( mType & RENDER_BLOCK ) fossil_print(" BLOCK"); | |
| 1992 | + if( mType & RENDER_BADLINK ) fossil_print(" BADLINK"); | |
| 1993 | + if( mType & RENDER_BADTARGET ) fossil_print(" BADTARGET"); | |
| 1994 | + if( mType & RENDER_BADTAG ) fossil_print(" BADTAG"); | |
| 1995 | + if( mType & RENDER_BADENTITY ) fossil_print(" BADENTITY"); | |
| 1996 | + if( mType & RENDER_BADHTML ) fossil_print(" BADHTML"); | |
| 1997 | + if( mType & RENDER_ERROR ) fossil_print(" ERROR"); | |
| 1998 | + fossil_print("\n"); | |
| 1999 | + } | |
| 1926 | 2000 | } |
| 1927 | 2001 | |
| 1928 | 2002 | /* |
| 1929 | 2003 | ** COMMAND: test-markdown-render |
| 1930 | 2004 | ** |
| @@ -2024,12 +2098,14 @@ | ||
| 2024 | 2098 | ** [target|...] |
| 2025 | 2099 | ** |
| 2026 | 2100 | ** Where "target" can be either an artifact ID prefix or a wiki page |
| 2027 | 2101 | ** name. For each such hyperlink found, add an entry to the |
| 2028 | 2102 | ** backlink table. |
| 2103 | +** | |
| 2104 | +** The return value is a mask of RENDER_ flags. | |
| 2029 | 2105 | */ |
| 2030 | -void wiki_extract_links( | |
| 2106 | +int wiki_extract_links( | |
| 2031 | 2107 | char *z, /* The wiki text from which to extract links */ |
| 2032 | 2108 | Backlink *pBklnk, /* Backlink extraction context */ |
| 2033 | 2109 | int flags /* wiki parsing flags */ |
| 2034 | 2110 | ){ |
| 2035 | 2111 | Renderer renderer; |
| @@ -2175,10 +2251,11 @@ | ||
| 2175 | 2251 | } |
| 2176 | 2252 | } |
| 2177 | 2253 | z += n; |
| 2178 | 2254 | } |
| 2179 | 2255 | free(renderer.aStack); |
| 2256 | + return renderer.mRender; | |
| 2180 | 2257 | } |
| 2181 | 2258 | |
| 2182 | 2259 | /* |
| 2183 | 2260 | ** Return the length, in bytes, of the HTML token that z is pointing to. |
| 2184 | 2261 | */ |
| 2185 | 2262 |
| --- src/wikiformat.c | |
| +++ src/wikiformat.c | |
| @@ -23,23 +23,41 @@ | |
| 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 | #define WIKI_NEWLINE 0x040 /* Honor \n - break lines at each \n */ |
| 35 | #define WIKI_MARKDOWNLINKS 0x080 /* Resolve hyperlinks as in markdown */ |
| 36 | #define WIKI_SAFE 0x100 /* Make the result safe for embedding */ |
| 37 | #define WIKI_TARGET_BLANK 0x200 /* Hyperlinks go to a new window */ |
| 38 | #define WIKI_NOBRACKET 0x400 /* Omit extra [..] around hyperlinks */ |
| 39 | #define WIKI_ADMIN 0x800 /* Ignore g.perm.Hyperlink */ |
| 40 | #endif |
| 41 | |
| 42 | |
| 43 | /* |
| 44 | ** These are the only markup attributes allowed. |
| 45 | */ |
| @@ -455,10 +473,11 @@ | |
| 455 | */ |
| 456 | typedef struct Renderer Renderer; |
| 457 | struct Renderer { |
| 458 | Blob *pOut; /* Output appended to this blob */ |
| 459 | int state; /* Flag that govern rendering */ |
| 460 | unsigned renderFlags; /* Flags from the client */ |
| 461 | int wikiList; /* Current wiki list type */ |
| 462 | int inVerbatim; /* True in <verbatim> mode */ |
| 463 | int preVerbState; /* Value of state prior to verbatim */ |
| 464 | int wantAutoParagraph; /* True if a <p> is desired */ |
| @@ -680,21 +699,26 @@ | |
| 680 | static int nextWikiToken(const char *z, Renderer *p, int *pTokenType){ |
| 681 | int n; |
| 682 | if( z[0]=='<' ){ |
| 683 | n = html_tag_length(z); |
| 684 | if( n>0 ){ |
| 685 | *pTokenType = TOKEN_MARKUP; |
| 686 | return n; |
| 687 | }else{ |
| 688 | *pTokenType = TOKEN_CHARACTER; |
| 689 | return 1; |
| 690 | } |
| 691 | } |
| 692 | if( z[0]=='&' && (p->inVerbatim || !isElement(z)) ){ |
| 693 | *pTokenType = TOKEN_CHARACTER; |
| 694 | return 1; |
| 695 | } |
| 696 | if( (p->state & ALLOW_WIKI)!=0 ){ |
| 697 | if( z[0]=='\n' ){ |
| 698 | n = paragraphBreakLength(z); |
| 699 | if( n>0 ){ |
| 700 | *pTokenType = TOKEN_PARAGRAPH; |
| @@ -726,17 +750,25 @@ | |
| 726 | if( n>0 ){ |
| 727 | *pTokenType = TOKEN_INDENT; |
| 728 | return n; |
| 729 | } |
| 730 | } |
| 731 | if( z[0]=='[' && (n = linkLength(z))>0 ){ |
| 732 | *pTokenType = TOKEN_LINK; |
| 733 | return n; |
| 734 | } |
| 735 | }else if( (p->state & ALLOW_LINKS)!=0 && z[0]=='[' && (n = linkLength(z))>0 ){ |
| 736 | *pTokenType = TOKEN_LINK; |
| 737 | return n; |
| 738 | } |
| 739 | *pTokenType = TOKEN_TEXT; |
| 740 | return 1 + textLength(z+1, p->state); |
| 741 | } |
| 742 | |
| @@ -746,13 +778,17 @@ | |
| 746 | ** z points to the start of a token. Return the number of |
| 747 | ** characters in that token. Write the token type into *pTokenType. |
| 748 | */ |
| 749 | static int nextRawToken(const char *z, Renderer *p, int *pTokenType){ |
| 750 | int n; |
| 751 | if( z[0]=='[' && (n = linkLength(z))>0 ){ |
| 752 | *pTokenType = TOKEN_LINK; |
| 753 | return n; |
| 754 | } |
| 755 | *pTokenType = TOKEN_RAW; |
| 756 | return 1 + textLength(z+1, p->state); |
| 757 | } |
| 758 | |
| @@ -1249,12 +1285,16 @@ | |
| 1249 | ** [wiki:WikiPageName] |
| 1250 | ** |
| 1251 | ** [2010-02-27 07:13] |
| 1252 | ** |
| 1253 | ** [InterMap:Link] -> Interwiki link |
| 1254 | */ |
| 1255 | void wiki_resolve_hyperlink( |
| 1256 | Blob *pOut, /* Write the HTML output here */ |
| 1257 | int mFlags, /* Rendering option flags */ |
| 1258 | const char *zTarget, /* Hyperlink target; text within [...] */ |
| 1259 | char *zClose, /* Write hyperlink closing text here */ |
| 1260 | int nClose, /* Bytes available in zClose[] */ |
| @@ -1264,10 +1304,11 @@ | |
| 1264 | const char *zTerm = "</a>"; |
| 1265 | const char *z; |
| 1266 | char *zExtra = 0; |
| 1267 | const char *zExtraNS = 0; |
| 1268 | char *zRemote = 0; |
| 1269 | |
| 1270 | if( zTitle ){ |
| 1271 | zExtra = mprintf(" title='%h'", zTitle); |
| 1272 | zExtraNS = zExtra+1; |
| 1273 | }else if( mFlags & WIKI_TARGET_BLANK ){ |
| @@ -1321,10 +1362,11 @@ | |
| 1321 | zTerm = ""; |
| 1322 | }else{ |
| 1323 | blob_appendf(pOut, "<span class=\"brokenlink\">%s", zLB); |
| 1324 | zTerm = "]</span>"; |
| 1325 | } |
| 1326 | }else if( g.perm.Hyperlink || (mFlags & WIKI_ADMIN)!=0 ){ |
| 1327 | blob_appendf(pOut, "%z%s",xhref(zExtraNS, "%R/info/%s", zTarget), zLB); |
| 1328 | zTerm = "]</a>"; |
| 1329 | }else{ |
| 1330 | zTerm = ""; |
| @@ -1341,11 +1383,11 @@ | |
| 1341 | }else{ |
| 1342 | blob_appendf(pOut, "<a href=\"%R/wiki?name=%T\"%s>", z, zExtra); |
| 1343 | } |
| 1344 | }else if( strlen(zTarget)>=10 && fossil_isdigit(zTarget[0]) && zTarget[4]=='-' |
| 1345 | && db_int(0, "SELECT datetime(%Q) NOT NULL", zTarget) ){ |
| 1346 | /* Dates or date-and-times in ISO8610 resolve to a link to the |
| 1347 | ** timeline for that date */ |
| 1348 | blob_appendf(pOut, "<a href=\"%R/timeline?c=%T\"%s>", zTarget, zExtra); |
| 1349 | }else if( mFlags & WIKI_MARKDOWNLINKS ){ |
| 1350 | /* If none of the above, and if rendering links for markdown, then |
| 1351 | ** create a link to the literal text of the target */ |
| @@ -1357,17 +1399,20 @@ | |
| 1357 | ** hyperlink. Just ignore it. */ |
| 1358 | zTerm = ""; |
| 1359 | }else if( (mFlags & (WIKI_NOBADLINKS|WIKI_LINKSONLY))!=0 ){ |
| 1360 | /* Also ignore the link if various flags are set */ |
| 1361 | zTerm = ""; |
| 1362 | }else{ |
| 1363 | blob_appendf(pOut, "<span class=\"brokenlink\">[%h]", zTarget); |
| 1364 | zTerm = "</span>"; |
| 1365 | } |
| 1366 | if( zExtra ) fossil_free(zExtra); |
| 1367 | assert( (int)strlen(zTerm)<nClose ); |
| 1368 | sqlite3_snprintf(nClose, zClose, "%s", zTerm); |
| 1369 | } |
| 1370 | |
| 1371 | /* |
| 1372 | ** Check zTarget to see if it looks like a valid hyperlink target. |
| 1373 | ** Return true if it does seem valid and false if not. |
| @@ -1508,10 +1553,11 @@ | |
| 1508 | } |
| 1509 | p->state |= AT_NEWLINE; |
| 1510 | break; |
| 1511 | } |
| 1512 | case TOKEN_BUL_LI: { |
| 1513 | if( inlineOnly ){ |
| 1514 | blob_append_string(p->pOut, " • "); |
| 1515 | }else{ |
| 1516 | if( p->wikiList!=MARKUP_UL ){ |
| 1517 | if( p->wikiList ){ |
| @@ -1528,10 +1574,11 @@ | |
| 1528 | blob_append_string(p->pOut, "<li>"); |
| 1529 | } |
| 1530 | break; |
| 1531 | } |
| 1532 | case TOKEN_NUM_LI: { |
| 1533 | if( inlineOnly ){ |
| 1534 | blob_append_string(p->pOut, " # "); |
| 1535 | }else{ |
| 1536 | if( p->wikiList!=MARKUP_OL ){ |
| 1537 | if( p->wikiList ){ |
| @@ -1548,10 +1595,11 @@ | |
| 1548 | blob_append_string(p->pOut, "<li>"); |
| 1549 | } |
| 1550 | break; |
| 1551 | } |
| 1552 | case TOKEN_ENUM: { |
| 1553 | if( inlineOnly ){ |
| 1554 | blob_appendf(p->pOut, " (%d) ", atoi(z)); |
| 1555 | }else{ |
| 1556 | if( p->wikiList!=MARKUP_OL ){ |
| 1557 | if( p->wikiList ){ |
| @@ -1568,10 +1616,11 @@ | |
| 1568 | blob_appendf(p->pOut, "<li value=\"%d\">", atoi(z)); |
| 1569 | } |
| 1570 | break; |
| 1571 | } |
| 1572 | case TOKEN_INDENT: { |
| 1573 | if( !inlineOnly ){ |
| 1574 | assert( p->wikiList==0 ); |
| 1575 | pushStack(p, MARKUP_BLOCKQUOTE); |
| 1576 | blob_append_string(p->pOut, "<blockquote>"); |
| 1577 | p->wantAutoParagraph = 0; |
| @@ -1596,10 +1645,11 @@ | |
| 1596 | char zClose[20]; |
| 1597 | char cS1 = 0; |
| 1598 | int iS1 = 0; |
| 1599 | |
| 1600 | startAutoParagraph(p); |
| 1601 | zTarget = &z[1]; |
| 1602 | for(i=1; z[i] && z[i]!=']'; i++){ |
| 1603 | if( z[i]=='|' && zDisplay==0 ){ |
| 1604 | zDisplay = &z[i+1]; |
| 1605 | for(j=i; j>0 && fossil_isspace(z[j-1]); j--){} |
| @@ -1612,11 +1662,11 @@ | |
| 1612 | if( zDisplay==0 ){ |
| 1613 | zDisplay = zTarget + interwiki_removable_prefix(zTarget); |
| 1614 | }else{ |
| 1615 | while( fossil_isspace(*zDisplay) ) zDisplay++; |
| 1616 | } |
| 1617 | wiki_resolve_hyperlink(p->pOut, p->state, |
| 1618 | zTarget, zClose, sizeof(zClose), zOrig, 0); |
| 1619 | if( linksOnly || zClose[0]==0 || p->inVerbatim ){ |
| 1620 | if( cS1 ) z[iS1] = cS1; |
| 1621 | if( zClose[0]!=']' ){ |
| 1622 | blob_appendf(p->pOut, "[%h]%s", zTarget, zClose); |
| @@ -1702,10 +1752,11 @@ | |
| 1702 | |
| 1703 | /* Render invalid markup literally. The markup appears in the |
| 1704 | ** final output as plain text. |
| 1705 | */ |
| 1706 | if( markup.iCode==MARKUP_INVALID ){ |
| 1707 | unparseMarkup(&markup); |
| 1708 | startAutoParagraph(p); |
| 1709 | blob_append_string(p->pOut, "<"); |
| 1710 | n = 1; |
| 1711 | }else |
| @@ -1814,10 +1865,11 @@ | |
| 1814 | }else |
| 1815 | { |
| 1816 | if( markup.iType==MUTYPE_FONT ){ |
| 1817 | startAutoParagraph(p); |
| 1818 | }else if( markup.iType==MUTYPE_BLOCK || markup.iType==MUTYPE_LIST ){ |
| 1819 | p->wantAutoParagraph = 0; |
| 1820 | } |
| 1821 | if( markup.iCode==MARKUP_HR |
| 1822 | || markup.iCode==MARKUP_H1 |
| 1823 | || markup.iCode==MARKUP_H2 |
| @@ -1844,12 +1896,14 @@ | |
| 1844 | ** Transform the text in the pIn blob. Write the results |
| 1845 | ** into the pOut blob. The pOut blob should already be |
| 1846 | ** initialized. The output is merely appended to pOut. |
| 1847 | ** If pOut is NULL, then the output is appended to the CGI |
| 1848 | ** reply. |
| 1849 | */ |
| 1850 | void wiki_convert(Blob *pIn, Blob *pOut, int flags){ |
| 1851 | Renderer renderer; |
| 1852 | |
| 1853 | memset(&renderer, 0, sizeof(renderer)); |
| 1854 | renderer.renderFlags = flags; |
| 1855 | renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH|flags; |
| @@ -1873,10 +1927,11 @@ | |
| 1873 | while( renderer.nStack ){ |
| 1874 | popStack(&renderer); |
| 1875 | } |
| 1876 | blob_append_char(renderer.pOut, '\n'); |
| 1877 | free(renderer.aStack); |
| 1878 | } |
| 1879 | |
| 1880 | /* |
| 1881 | ** COMMAND: test-wiki-render |
| 1882 | ** |
| @@ -1892,15 +1947,18 @@ | |
| 1892 | ** --inline Set the WIKI_INLINE flag |
| 1893 | ** --linksonly Set the WIKI_LINKSONLY flag |
| 1894 | ** --nobadlinks Set the WIKI_NOBADLINKS flag |
| 1895 | ** --noblock Set the WIKI_NOBLOCK flag |
| 1896 | ** --text Run the output through html_to_plaintext(). |
| 1897 | */ |
| 1898 | void test_wiki_render(void){ |
| 1899 | Blob in, out; |
| 1900 | int flags = 0; |
| 1901 | int bText; |
| 1902 | if( find_option("buttons",0,0)!=0 ) flags |= WIKI_BUTTONS; |
| 1903 | if( find_option("htmlonly",0,0)!=0 ) flags |= WIKI_HTMLONLY; |
| 1904 | if( find_option("linksonly",0,0)!=0 ) flags |= WIKI_LINKSONLY; |
| 1905 | if( find_option("nobadlinks",0,0)!=0 ) flags |= WIKI_NOBADLINKS; |
| 1906 | if( find_option("inline",0,0)!=0 ) flags |= WIKI_INLINE; |
| @@ -1907,24 +1965,40 @@ | |
| 1907 | if( find_option("noblock",0,0)!=0 ) flags |= WIKI_NOBLOCK; |
| 1908 | if( find_option("dark-pikchr",0,0)!=0 ){ |
| 1909 | pikchr_to_html_add_flags( PIKCHR_PROCESS_DARK_MODE ); |
| 1910 | } |
| 1911 | bText = find_option("text",0,0)!=0; |
| 1912 | db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0); |
| 1913 | verify_all_options(); |
| 1914 | if( g.argc!=3 ) usage("FILE"); |
| 1915 | blob_zero(&out); |
| 1916 | blob_read_from_file(&in, g.argv[2], ExtFILE); |
| 1917 | wiki_convert(&in, &out, flags); |
| 1918 | if( bText ){ |
| 1919 | Blob txt; |
| 1920 | blob_init(&txt, 0, 0); |
| 1921 | html_to_plaintext(blob_str(&out),&txt); |
| 1922 | blob_reset(&out); |
| 1923 | out = txt; |
| 1924 | } |
| 1925 | blob_write_to_file(&out, "-"); |
| 1926 | } |
| 1927 | |
| 1928 | /* |
| 1929 | ** COMMAND: test-markdown-render |
| 1930 | ** |
| @@ -2024,12 +2098,14 @@ | |
| 2024 | ** [target|...] |
| 2025 | ** |
| 2026 | ** Where "target" can be either an artifact ID prefix or a wiki page |
| 2027 | ** name. For each such hyperlink found, add an entry to the |
| 2028 | ** backlink table. |
| 2029 | */ |
| 2030 | void wiki_extract_links( |
| 2031 | char *z, /* The wiki text from which to extract links */ |
| 2032 | Backlink *pBklnk, /* Backlink extraction context */ |
| 2033 | int flags /* wiki parsing flags */ |
| 2034 | ){ |
| 2035 | Renderer renderer; |
| @@ -2175,10 +2251,11 @@ | |
| 2175 | } |
| 2176 | } |
| 2177 | z += n; |
| 2178 | } |
| 2179 | free(renderer.aStack); |
| 2180 | } |
| 2181 | |
| 2182 | /* |
| 2183 | ** Return the length, in bytes, of the HTML token that z is pointing to. |
| 2184 | */ |
| 2185 |
| --- src/wikiformat.c | |
| +++ src/wikiformat.c | |
| @@ -23,23 +23,41 @@ | |
| 23 | |
| 24 | #if INTERFACE |
| 25 | /* |
| 26 | ** Allowed wiki transformation operations |
| 27 | */ |
| 28 | #define WIKI_HTMLONLY 0x0001 /* HTML markup only. No wiki */ |
| 29 | #define WIKI_INLINE 0x0002 /* Do not surround with <p>..</p> */ |
| 30 | #define WIKI_NOBLOCK 0x0004 /* No block markup of any kind */ |
| 31 | #define WIKI_BUTTONS 0x0008 /* Allow sub-menu buttons */ |
| 32 | #define WIKI_NOBADLINKS 0x0010 /* Ignore broken hyperlinks */ |
| 33 | #define WIKI_LINKSONLY 0x0020 /* No markup. Only decorate links */ |
| 34 | #define WIKI_NEWLINE 0x0040 /* Honor \n - break lines at each \n */ |
| 35 | #define WIKI_MARKDOWNLINKS 0x0080 /* Resolve hyperlinks as in markdown */ |
| 36 | #define WIKI_SAFE 0x0100 /* Make the result safe for embedding */ |
| 37 | #define WIKI_TARGET_BLANK 0x0200 /* Hyperlinks go to a new window */ |
| 38 | #define WIKI_NOBRACKET 0x0400 /* Omit extra [..] around hyperlinks */ |
| 39 | #define WIKI_ADMIN 0x0800 /* Ignore g.perm.Hyperlink */ |
| 40 | |
| 41 | /* |
| 42 | ** Return values from wiki_convert |
| 43 | */ |
| 44 | #define RENDER_LINK 0x0001 /* One or more hyperlinks rendered */ |
| 45 | #define RENDER_ENTITY 0x0002 /* One or more HTML entities (ex: <) */ |
| 46 | #define RENDER_TAG 0x0004 /* One or more HTML tags */ |
| 47 | #define RENDER_BLOCKTAG 0x0008 /* One or more HTML block tags (ex: <p>) */ |
| 48 | #define RENDER_BLOCK 0x0010 /* Block wiki (paragraphs, etc.) */ |
| 49 | #define RENDER_BADLINK 0x0100 /* Bad hyperlink syntax seen */ |
| 50 | #define RENDER_BADTARGET 0x0200 /* Bad hyperlink target */ |
| 51 | #define RENDER_BADTAG 0x0400 /* Bad HTML tag or tag syntax */ |
| 52 | #define RENDER_BADENTITY 0x0800 /* Bad HTML entity syntax */ |
| 53 | #define RENDER_BADHTML 0x1000 /* Bad HTML seen */ |
| 54 | #define RENDER_ERROR 0x8000 /* Some other kind of error */ |
| 55 | /* Composite values: */ |
| 56 | #define RENDER_ANYERROR 0x9f00 /* Mask for any kind of error */ |
| 57 | |
| 58 | #endif /* INTERFACE */ |
| 59 | |
| 60 | |
| 61 | /* |
| 62 | ** These are the only markup attributes allowed. |
| 63 | */ |
| @@ -455,10 +473,11 @@ | |
| 473 | */ |
| 474 | typedef struct Renderer Renderer; |
| 475 | struct Renderer { |
| 476 | Blob *pOut; /* Output appended to this blob */ |
| 477 | int state; /* Flag that govern rendering */ |
| 478 | int mRender; /* Mask of RENDER_* values to return */ |
| 479 | unsigned renderFlags; /* Flags from the client */ |
| 480 | int wikiList; /* Current wiki list type */ |
| 481 | int inVerbatim; /* True in <verbatim> mode */ |
| 482 | int preVerbState; /* Value of state prior to verbatim */ |
| 483 | int wantAutoParagraph; /* True if a <p> is desired */ |
| @@ -680,21 +699,26 @@ | |
| 699 | static int nextWikiToken(const char *z, Renderer *p, int *pTokenType){ |
| 700 | int n; |
| 701 | if( z[0]=='<' ){ |
| 702 | n = html_tag_length(z); |
| 703 | if( n>0 ){ |
| 704 | p->mRender |= RENDER_TAG; |
| 705 | *pTokenType = TOKEN_MARKUP; |
| 706 | return n; |
| 707 | }else{ |
| 708 | p->mRender |= RENDER_BADTAG; |
| 709 | *pTokenType = TOKEN_CHARACTER; |
| 710 | return 1; |
| 711 | } |
| 712 | } |
| 713 | if( z[0]=='&' ){ |
| 714 | p->mRender |= RENDER_ENTITY; |
| 715 | if( (p->inVerbatim || !isElement(z)) ){ |
| 716 | *pTokenType = TOKEN_CHARACTER; |
| 717 | return 1; |
| 718 | } |
| 719 | } |
| 720 | if( (p->state & ALLOW_WIKI)!=0 ){ |
| 721 | if( z[0]=='\n' ){ |
| 722 | n = paragraphBreakLength(z); |
| 723 | if( n>0 ){ |
| 724 | *pTokenType = TOKEN_PARAGRAPH; |
| @@ -726,17 +750,25 @@ | |
| 750 | if( n>0 ){ |
| 751 | *pTokenType = TOKEN_INDENT; |
| 752 | return n; |
| 753 | } |
| 754 | } |
| 755 | if( z[0]=='[' ){ |
| 756 | if( (n = linkLength(z))>0 ){ |
| 757 | *pTokenType = TOKEN_LINK; |
| 758 | return n; |
| 759 | }else{ |
| 760 | p->mRender |= RENDER_BADLINK; |
| 761 | } |
| 762 | } |
| 763 | }else if( (p->state & ALLOW_LINKS)!=0 && z[0]=='[' ){ |
| 764 | if( (n = linkLength(z))>0 ){ |
| 765 | *pTokenType = TOKEN_LINK; |
| 766 | return n; |
| 767 | }else{ |
| 768 | p->mRender |= RENDER_BADLINK; |
| 769 | } |
| 770 | } |
| 771 | *pTokenType = TOKEN_TEXT; |
| 772 | return 1 + textLength(z+1, p->state); |
| 773 | } |
| 774 | |
| @@ -746,13 +778,17 @@ | |
| 778 | ** z points to the start of a token. Return the number of |
| 779 | ** characters in that token. Write the token type into *pTokenType. |
| 780 | */ |
| 781 | static int nextRawToken(const char *z, Renderer *p, int *pTokenType){ |
| 782 | int n; |
| 783 | if( z[0]=='[' ){ |
| 784 | if( (n = linkLength(z))>0 ){ |
| 785 | *pTokenType = TOKEN_LINK; |
| 786 | return n; |
| 787 | }else{ |
| 788 | p->mRender |= RENDER_BADLINK; |
| 789 | } |
| 790 | } |
| 791 | *pTokenType = TOKEN_RAW; |
| 792 | return 1 + textLength(z+1, p->state); |
| 793 | } |
| 794 | |
| @@ -1249,12 +1285,16 @@ | |
| 1285 | ** [wiki:WikiPageName] |
| 1286 | ** |
| 1287 | ** [2010-02-27 07:13] |
| 1288 | ** |
| 1289 | ** [InterMap:Link] -> Interwiki link |
| 1290 | ** |
| 1291 | ** The return value is a mask of RENDER_* values indicating what happened. |
| 1292 | ** Probably the return value is 0 on success and RENDER_BADTARGET or |
| 1293 | ** RENDER_BADLINK if there are problems. |
| 1294 | */ |
| 1295 | int wiki_resolve_hyperlink( |
| 1296 | Blob *pOut, /* Write the HTML output here */ |
| 1297 | int mFlags, /* Rendering option flags */ |
| 1298 | const char *zTarget, /* Hyperlink target; text within [...] */ |
| 1299 | char *zClose, /* Write hyperlink closing text here */ |
| 1300 | int nClose, /* Bytes available in zClose[] */ |
| @@ -1264,10 +1304,11 @@ | |
| 1304 | const char *zTerm = "</a>"; |
| 1305 | const char *z; |
| 1306 | char *zExtra = 0; |
| 1307 | const char *zExtraNS = 0; |
| 1308 | char *zRemote = 0; |
| 1309 | int rc = 0; |
| 1310 | |
| 1311 | if( zTitle ){ |
| 1312 | zExtra = mprintf(" title='%h'", zTitle); |
| 1313 | zExtraNS = zExtra+1; |
| 1314 | }else if( mFlags & WIKI_TARGET_BLANK ){ |
| @@ -1321,10 +1362,11 @@ | |
| 1362 | zTerm = ""; |
| 1363 | }else{ |
| 1364 | blob_appendf(pOut, "<span class=\"brokenlink\">%s", zLB); |
| 1365 | zTerm = "]</span>"; |
| 1366 | } |
| 1367 | rc |= RENDER_BADTARGET; |
| 1368 | }else if( g.perm.Hyperlink || (mFlags & WIKI_ADMIN)!=0 ){ |
| 1369 | blob_appendf(pOut, "%z%s",xhref(zExtraNS, "%R/info/%s", zTarget), zLB); |
| 1370 | zTerm = "]</a>"; |
| 1371 | }else{ |
| 1372 | zTerm = ""; |
| @@ -1341,11 +1383,11 @@ | |
| 1383 | }else{ |
| 1384 | blob_appendf(pOut, "<a href=\"%R/wiki?name=%T\"%s>", z, zExtra); |
| 1385 | } |
| 1386 | }else if( strlen(zTarget)>=10 && fossil_isdigit(zTarget[0]) && zTarget[4]=='-' |
| 1387 | && db_int(0, "SELECT datetime(%Q) NOT NULL", zTarget) ){ |
| 1388 | /* Dates or date-and-times in ISO8601 resolve to a link to the |
| 1389 | ** timeline for that date */ |
| 1390 | blob_appendf(pOut, "<a href=\"%R/timeline?c=%T\"%s>", zTarget, zExtra); |
| 1391 | }else if( mFlags & WIKI_MARKDOWNLINKS ){ |
| 1392 | /* If none of the above, and if rendering links for markdown, then |
| 1393 | ** create a link to the literal text of the target */ |
| @@ -1357,17 +1399,20 @@ | |
| 1399 | ** hyperlink. Just ignore it. */ |
| 1400 | zTerm = ""; |
| 1401 | }else if( (mFlags & (WIKI_NOBADLINKS|WIKI_LINKSONLY))!=0 ){ |
| 1402 | /* Also ignore the link if various flags are set */ |
| 1403 | zTerm = ""; |
| 1404 | rc |= RENDER_BADTARGET; |
| 1405 | }else{ |
| 1406 | blob_appendf(pOut, "<span class=\"brokenlink\">[%h]", zTarget); |
| 1407 | zTerm = "</span>"; |
| 1408 | rc |= RENDER_BADTARGET; |
| 1409 | } |
| 1410 | if( zExtra ) fossil_free(zExtra); |
| 1411 | assert( (int)strlen(zTerm)<nClose ); |
| 1412 | sqlite3_snprintf(nClose, zClose, "%s", zTerm); |
| 1413 | return rc; |
| 1414 | } |
| 1415 | |
| 1416 | /* |
| 1417 | ** Check zTarget to see if it looks like a valid hyperlink target. |
| 1418 | ** Return true if it does seem valid and false if not. |
| @@ -1508,10 +1553,11 @@ | |
| 1553 | } |
| 1554 | p->state |= AT_NEWLINE; |
| 1555 | break; |
| 1556 | } |
| 1557 | case TOKEN_BUL_LI: { |
| 1558 | p->mRender |= RENDER_BLOCK; |
| 1559 | if( inlineOnly ){ |
| 1560 | blob_append_string(p->pOut, " • "); |
| 1561 | }else{ |
| 1562 | if( p->wikiList!=MARKUP_UL ){ |
| 1563 | if( p->wikiList ){ |
| @@ -1528,10 +1574,11 @@ | |
| 1574 | blob_append_string(p->pOut, "<li>"); |
| 1575 | } |
| 1576 | break; |
| 1577 | } |
| 1578 | case TOKEN_NUM_LI: { |
| 1579 | p->mRender |= RENDER_BLOCK; |
| 1580 | if( inlineOnly ){ |
| 1581 | blob_append_string(p->pOut, " # "); |
| 1582 | }else{ |
| 1583 | if( p->wikiList!=MARKUP_OL ){ |
| 1584 | if( p->wikiList ){ |
| @@ -1548,10 +1595,11 @@ | |
| 1595 | blob_append_string(p->pOut, "<li>"); |
| 1596 | } |
| 1597 | break; |
| 1598 | } |
| 1599 | case TOKEN_ENUM: { |
| 1600 | p->mRender |= RENDER_BLOCK; |
| 1601 | if( inlineOnly ){ |
| 1602 | blob_appendf(p->pOut, " (%d) ", atoi(z)); |
| 1603 | }else{ |
| 1604 | if( p->wikiList!=MARKUP_OL ){ |
| 1605 | if( p->wikiList ){ |
| @@ -1568,10 +1616,11 @@ | |
| 1616 | blob_appendf(p->pOut, "<li value=\"%d\">", atoi(z)); |
| 1617 | } |
| 1618 | break; |
| 1619 | } |
| 1620 | case TOKEN_INDENT: { |
| 1621 | p->mRender |= RENDER_BLOCK; |
| 1622 | if( !inlineOnly ){ |
| 1623 | assert( p->wikiList==0 ); |
| 1624 | pushStack(p, MARKUP_BLOCKQUOTE); |
| 1625 | blob_append_string(p->pOut, "<blockquote>"); |
| 1626 | p->wantAutoParagraph = 0; |
| @@ -1596,10 +1645,11 @@ | |
| 1645 | char zClose[20]; |
| 1646 | char cS1 = 0; |
| 1647 | int iS1 = 0; |
| 1648 | |
| 1649 | startAutoParagraph(p); |
| 1650 | p->mRender |= RENDER_LINK; |
| 1651 | zTarget = &z[1]; |
| 1652 | for(i=1; z[i] && z[i]!=']'; i++){ |
| 1653 | if( z[i]=='|' && zDisplay==0 ){ |
| 1654 | zDisplay = &z[i+1]; |
| 1655 | for(j=i; j>0 && fossil_isspace(z[j-1]); j--){} |
| @@ -1612,11 +1662,11 @@ | |
| 1662 | if( zDisplay==0 ){ |
| 1663 | zDisplay = zTarget + interwiki_removable_prefix(zTarget); |
| 1664 | }else{ |
| 1665 | while( fossil_isspace(*zDisplay) ) zDisplay++; |
| 1666 | } |
| 1667 | p->mRender |= wiki_resolve_hyperlink(p->pOut, p->state, |
| 1668 | zTarget, zClose, sizeof(zClose), zOrig, 0); |
| 1669 | if( linksOnly || zClose[0]==0 || p->inVerbatim ){ |
| 1670 | if( cS1 ) z[iS1] = cS1; |
| 1671 | if( zClose[0]!=']' ){ |
| 1672 | blob_appendf(p->pOut, "[%h]%s", zTarget, zClose); |
| @@ -1702,10 +1752,11 @@ | |
| 1752 | |
| 1753 | /* Render invalid markup literally. The markup appears in the |
| 1754 | ** final output as plain text. |
| 1755 | */ |
| 1756 | if( markup.iCode==MARKUP_INVALID ){ |
| 1757 | p->mRender |= RENDER_BADTAG; |
| 1758 | unparseMarkup(&markup); |
| 1759 | startAutoParagraph(p); |
| 1760 | blob_append_string(p->pOut, "<"); |
| 1761 | n = 1; |
| 1762 | }else |
| @@ -1814,10 +1865,11 @@ | |
| 1865 | }else |
| 1866 | { |
| 1867 | if( markup.iType==MUTYPE_FONT ){ |
| 1868 | startAutoParagraph(p); |
| 1869 | }else if( markup.iType==MUTYPE_BLOCK || markup.iType==MUTYPE_LIST ){ |
| 1870 | p->mRender |= RENDER_BLOCKTAG; |
| 1871 | p->wantAutoParagraph = 0; |
| 1872 | } |
| 1873 | if( markup.iCode==MARKUP_HR |
| 1874 | || markup.iCode==MARKUP_H1 |
| 1875 | || markup.iCode==MARKUP_H2 |
| @@ -1844,12 +1896,14 @@ | |
| 1896 | ** Transform the text in the pIn blob. Write the results |
| 1897 | ** into the pOut blob. The pOut blob should already be |
| 1898 | ** initialized. The output is merely appended to pOut. |
| 1899 | ** If pOut is NULL, then the output is appended to the CGI |
| 1900 | ** reply. |
| 1901 | ** |
| 1902 | ** Return a mask of RENDER_ flags indicating what happened. |
| 1903 | */ |
| 1904 | int wiki_convert(Blob *pIn, Blob *pOut, int flags){ |
| 1905 | Renderer renderer; |
| 1906 | |
| 1907 | memset(&renderer, 0, sizeof(renderer)); |
| 1908 | renderer.renderFlags = flags; |
| 1909 | renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH|flags; |
| @@ -1873,10 +1927,11 @@ | |
| 1927 | while( renderer.nStack ){ |
| 1928 | popStack(&renderer); |
| 1929 | } |
| 1930 | blob_append_char(renderer.pOut, '\n'); |
| 1931 | free(renderer.aStack); |
| 1932 | return renderer.mRender; |
| 1933 | } |
| 1934 | |
| 1935 | /* |
| 1936 | ** COMMAND: test-wiki-render |
| 1937 | ** |
| @@ -1892,15 +1947,18 @@ | |
| 1947 | ** --inline Set the WIKI_INLINE flag |
| 1948 | ** --linksonly Set the WIKI_LINKSONLY flag |
| 1949 | ** --nobadlinks Set the WIKI_NOBADLINKS flag |
| 1950 | ** --noblock Set the WIKI_NOBLOCK flag |
| 1951 | ** --text Run the output through html_to_plaintext(). |
| 1952 | ** --type Break down the return code from wiki_convert(). |
| 1953 | */ |
| 1954 | void test_wiki_render(void){ |
| 1955 | Blob in, out; |
| 1956 | int flags = 0; |
| 1957 | int bText; |
| 1958 | int showType = 0; |
| 1959 | int mType; |
| 1960 | if( find_option("buttons",0,0)!=0 ) flags |= WIKI_BUTTONS; |
| 1961 | if( find_option("htmlonly",0,0)!=0 ) flags |= WIKI_HTMLONLY; |
| 1962 | if( find_option("linksonly",0,0)!=0 ) flags |= WIKI_LINKSONLY; |
| 1963 | if( find_option("nobadlinks",0,0)!=0 ) flags |= WIKI_NOBADLINKS; |
| 1964 | if( find_option("inline",0,0)!=0 ) flags |= WIKI_INLINE; |
| @@ -1907,24 +1965,40 @@ | |
| 1965 | if( find_option("noblock",0,0)!=0 ) flags |= WIKI_NOBLOCK; |
| 1966 | if( find_option("dark-pikchr",0,0)!=0 ){ |
| 1967 | pikchr_to_html_add_flags( PIKCHR_PROCESS_DARK_MODE ); |
| 1968 | } |
| 1969 | bText = find_option("text",0,0)!=0; |
| 1970 | showType = find_option("type",0,0)!=0; |
| 1971 | db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0); |
| 1972 | verify_all_options(); |
| 1973 | if( g.argc!=3 ) usage("FILE"); |
| 1974 | blob_zero(&out); |
| 1975 | blob_read_from_file(&in, g.argv[2], ExtFILE); |
| 1976 | mType = wiki_convert(&in, &out, flags); |
| 1977 | if( bText ){ |
| 1978 | Blob txt; |
| 1979 | blob_init(&txt, 0, 0); |
| 1980 | html_to_plaintext(blob_str(&out),&txt); |
| 1981 | blob_reset(&out); |
| 1982 | out = txt; |
| 1983 | } |
| 1984 | blob_write_to_file(&out, "-"); |
| 1985 | if( showType ){ |
| 1986 | fossil_print("%.*c\nResult Codes:", terminal_get_width(80)-1, '*'); |
| 1987 | if( mType & RENDER_LINK ) fossil_print(" LINK"); |
| 1988 | if( mType & RENDER_ENTITY ) fossil_print(" ENTITY"); |
| 1989 | if( mType & RENDER_TAG ) fossil_print(" TAG"); |
| 1990 | if( mType & RENDER_BLOCKTAG ) fossil_print(" BLOCKTAG"); |
| 1991 | if( mType & RENDER_BLOCK ) fossil_print(" BLOCK"); |
| 1992 | if( mType & RENDER_BADLINK ) fossil_print(" BADLINK"); |
| 1993 | if( mType & RENDER_BADTARGET ) fossil_print(" BADTARGET"); |
| 1994 | if( mType & RENDER_BADTAG ) fossil_print(" BADTAG"); |
| 1995 | if( mType & RENDER_BADENTITY ) fossil_print(" BADENTITY"); |
| 1996 | if( mType & RENDER_BADHTML ) fossil_print(" BADHTML"); |
| 1997 | if( mType & RENDER_ERROR ) fossil_print(" ERROR"); |
| 1998 | fossil_print("\n"); |
| 1999 | } |
| 2000 | } |
| 2001 | |
| 2002 | /* |
| 2003 | ** COMMAND: test-markdown-render |
| 2004 | ** |
| @@ -2024,12 +2098,14 @@ | |
| 2098 | ** [target|...] |
| 2099 | ** |
| 2100 | ** Where "target" can be either an artifact ID prefix or a wiki page |
| 2101 | ** name. For each such hyperlink found, add an entry to the |
| 2102 | ** backlink table. |
| 2103 | ** |
| 2104 | ** The return value is a mask of RENDER_ flags. |
| 2105 | */ |
| 2106 | int wiki_extract_links( |
| 2107 | char *z, /* The wiki text from which to extract links */ |
| 2108 | Backlink *pBklnk, /* Backlink extraction context */ |
| 2109 | int flags /* wiki parsing flags */ |
| 2110 | ){ |
| 2111 | Renderer renderer; |
| @@ -2175,10 +2251,11 @@ | |
| 2251 | } |
| 2252 | } |
| 2253 | z += n; |
| 2254 | } |
| 2255 | free(renderer.aStack); |
| 2256 | return renderer.mRender; |
| 2257 | } |
| 2258 | |
| 2259 | /* |
| 2260 | ** Return the length, in bytes, of the HTML token that z is pointing to. |
| 2261 | */ |
| 2262 |