Fossil SCM
Automatically clean up the HTML that is generated by webbrowsers and by the Fossil wiki-to-html translator so that the HTML is easier to read in the wysiwyg editor.
Commit
fb6f1b7b719f9f62322b6eb950ccf72cb86de4c6
Parent
6cd9847f5479336…
2 files changed
+14
-3
+111
+14
-3
| --- src/wiki.c | ||
| +++ src/wiki.c | ||
| @@ -262,13 +262,21 @@ | ||
| 262 | 262 | const char *zPageName; |
| 263 | 263 | char *zHtmlPageName; |
| 264 | 264 | int n; |
| 265 | 265 | const char *z; |
| 266 | 266 | char *zBody = (char*)P("w"); |
| 267 | + int isWysiwyg = P("wysiwyg")!=0; | |
| 267 | 268 | |
| 268 | 269 | if( zBody ){ |
| 269 | - zBody = mprintf("%s", zBody); | |
| 270 | + if( isWysiwyg ){ | |
| 271 | + Blob body; | |
| 272 | + blob_zero(&body); | |
| 273 | + htmlTidy(zBody, &body); | |
| 274 | + zBody = blob_str(&body); | |
| 275 | + }else{ | |
| 276 | + zBody = mprintf("%s", zBody); | |
| 277 | + } | |
| 270 | 278 | } |
| 271 | 279 | login_check_credentials(); |
| 272 | 280 | zPageName = PD("name",""); |
| 273 | 281 | if( check_name(zPageName) ) return; |
| 274 | 282 | isSandbox = is_sandbox(zPageName); |
| @@ -360,16 +368,19 @@ | ||
| 360 | 368 | @ rows="%d(n)" wrap="virtual">%h(zBody)</textarea> |
| 361 | 369 | @ <br /> |
| 362 | 370 | @ <input type="submit" name="preview" value="Preview Your Changes" /> |
| 363 | 371 | }else{ |
| 364 | 372 | /* Wysiwyg editing */ |
| 365 | - Blob html; | |
| 373 | + Blob html, temp; | |
| 366 | 374 | @ <form method="post" action="%s(g.zTop)/wikiedit" |
| 367 | 375 | @ onsubmit="wysiwygSubmit()"><div> |
| 368 | 376 | @ <input type="hidden" name="wysiwyg" value="1" /> |
| 377 | + blob_zero(&temp); | |
| 378 | + wiki_convert(&wiki, &temp, 0); | |
| 369 | 379 | blob_zero(&html); |
| 370 | - wiki_convert(&wiki, &html, 0); | |
| 380 | + htmlTidy(blob_str(&temp), &html); | |
| 381 | + blob_reset(&temp); | |
| 371 | 382 | wysiwygEditor("w", blob_str(&html), 60, n); |
| 372 | 383 | blob_reset(&html); |
| 373 | 384 | @ <br /> |
| 374 | 385 | } |
| 375 | 386 | @ <input type="submit" name="submit" value="Apply These Changes" /> |
| 376 | 387 |
| --- src/wiki.c | |
| +++ src/wiki.c | |
| @@ -262,13 +262,21 @@ | |
| 262 | const char *zPageName; |
| 263 | char *zHtmlPageName; |
| 264 | int n; |
| 265 | const char *z; |
| 266 | char *zBody = (char*)P("w"); |
| 267 | |
| 268 | if( zBody ){ |
| 269 | zBody = mprintf("%s", zBody); |
| 270 | } |
| 271 | login_check_credentials(); |
| 272 | zPageName = PD("name",""); |
| 273 | if( check_name(zPageName) ) return; |
| 274 | isSandbox = is_sandbox(zPageName); |
| @@ -360,16 +368,19 @@ | |
| 360 | @ rows="%d(n)" wrap="virtual">%h(zBody)</textarea> |
| 361 | @ <br /> |
| 362 | @ <input type="submit" name="preview" value="Preview Your Changes" /> |
| 363 | }else{ |
| 364 | /* Wysiwyg editing */ |
| 365 | Blob html; |
| 366 | @ <form method="post" action="%s(g.zTop)/wikiedit" |
| 367 | @ onsubmit="wysiwygSubmit()"><div> |
| 368 | @ <input type="hidden" name="wysiwyg" value="1" /> |
| 369 | blob_zero(&html); |
| 370 | wiki_convert(&wiki, &html, 0); |
| 371 | wysiwygEditor("w", blob_str(&html), 60, n); |
| 372 | blob_reset(&html); |
| 373 | @ <br /> |
| 374 | } |
| 375 | @ <input type="submit" name="submit" value="Apply These Changes" /> |
| 376 |
| --- src/wiki.c | |
| +++ src/wiki.c | |
| @@ -262,13 +262,21 @@ | |
| 262 | const char *zPageName; |
| 263 | char *zHtmlPageName; |
| 264 | int n; |
| 265 | const char *z; |
| 266 | char *zBody = (char*)P("w"); |
| 267 | int isWysiwyg = P("wysiwyg")!=0; |
| 268 | |
| 269 | if( zBody ){ |
| 270 | if( isWysiwyg ){ |
| 271 | Blob body; |
| 272 | blob_zero(&body); |
| 273 | htmlTidy(zBody, &body); |
| 274 | zBody = blob_str(&body); |
| 275 | }else{ |
| 276 | zBody = mprintf("%s", zBody); |
| 277 | } |
| 278 | } |
| 279 | login_check_credentials(); |
| 280 | zPageName = PD("name",""); |
| 281 | if( check_name(zPageName) ) return; |
| 282 | isSandbox = is_sandbox(zPageName); |
| @@ -360,16 +368,19 @@ | |
| 368 | @ rows="%d(n)" wrap="virtual">%h(zBody)</textarea> |
| 369 | @ <br /> |
| 370 | @ <input type="submit" name="preview" value="Preview Your Changes" /> |
| 371 | }else{ |
| 372 | /* Wysiwyg editing */ |
| 373 | Blob html, temp; |
| 374 | @ <form method="post" action="%s(g.zTop)/wikiedit" |
| 375 | @ onsubmit="wysiwygSubmit()"><div> |
| 376 | @ <input type="hidden" name="wysiwyg" value="1" /> |
| 377 | blob_zero(&temp); |
| 378 | wiki_convert(&wiki, &temp, 0); |
| 379 | blob_zero(&html); |
| 380 | htmlTidy(blob_str(&temp), &html); |
| 381 | blob_reset(&temp); |
| 382 | wysiwygEditor("w", blob_str(&html), 60, n); |
| 383 | blob_reset(&html); |
| 384 | @ <br /> |
| 385 | } |
| 386 | @ <input type="submit" name="submit" value="Apply These Changes" /> |
| 387 |
+111
| --- src/wikiformat.c | ||
| +++ src/wikiformat.c | ||
| @@ -1747,5 +1747,116 @@ | ||
| 1747 | 1747 | } |
| 1748 | 1748 | z += n; |
| 1749 | 1749 | } |
| 1750 | 1750 | free(renderer.aStack); |
| 1751 | 1751 | } |
| 1752 | + | |
| 1753 | +/* | |
| 1754 | +** Get the next HTML token. | |
| 1755 | +** | |
| 1756 | +** z points to the start of a token. Return the number of | |
| 1757 | +** characters in that token. | |
| 1758 | +*/ | |
| 1759 | +static int nextHtmlToken(const char *z){ | |
| 1760 | + int n; | |
| 1761 | + if( z[0]=='<' ){ | |
| 1762 | + n = markupLength(z); | |
| 1763 | + if( n<=0 ) n = 1; | |
| 1764 | + }else if( fossil_isspace(z[0]) ){ | |
| 1765 | + for(n=1; z[n] && fossil_isspace(z[n]); n++){} | |
| 1766 | + }else{ | |
| 1767 | + for(n=1; z[n] && z[n]!='<' && !fossil_isspace(z[n]); n++){} | |
| 1768 | + } | |
| 1769 | + return n; | |
| 1770 | +} | |
| 1771 | + | |
| 1772 | +/* | |
| 1773 | +** Return true if z[] is the word zWord in any case. | |
| 1774 | +*/ | |
| 1775 | +static int isWord(const char *z, const char *zWord, int nWord){ | |
| 1776 | + return fossil_strnicmp(z, zWord, nWord)==0 && !fossil_isalpha(z[nWord]); | |
| 1777 | +} | |
| 1778 | + | |
| 1779 | +/* | |
| 1780 | +** Attempt to reformat messy HTML to be easily readable by humans. | |
| 1781 | +** | |
| 1782 | +** * Try to keep lines less than 80 characters in length | |
| 1783 | +** * Collapse white space into a single space | |
| 1784 | +** * Put a blank line before: | |
| 1785 | +** <blockquote><center><code><hN><p><pre><table> | |
| 1786 | +** * Put a newline after <br> and <hr> | |
| 1787 | +** * Start each of the following elements on a new line: | |
| 1788 | +** <address><cite><dd><div><dl><dt><li><ol><samp> | |
| 1789 | +** <tbody><td><tfoot><th><thead><tr><ul> | |
| 1790 | +** | |
| 1791 | +** Except, do not do any reformatting inside of <pre>...</pre> | |
| 1792 | +*/ | |
| 1793 | +void htmlTidy(const char *zIn, Blob *pOut){ | |
| 1794 | + int n; | |
| 1795 | + int nPre = 0; | |
| 1796 | + int iCur = 0; | |
| 1797 | + while( zIn[0] ){ | |
| 1798 | + n = nextHtmlToken(zIn); | |
| 1799 | + if( zIn[0]=='<' && n>1 ){ | |
| 1800 | + if( isWord(zIn, "<pre", 4) ){ | |
| 1801 | + if( iCur && nPre==0 ){ blob_append(pOut, "\n", 1); iCur = 0; } | |
| 1802 | + nPre++; | |
| 1803 | + }else if( isWord(zIn, "</pre", 5) ){ | |
| 1804 | + nPre--; | |
| 1805 | + if( nPre==0 ){ blob_append(pOut, "\n", 1); iCur = 0; } | |
| 1806 | + }else if( isWord(zIn, "<blockquote", 11) | |
| 1807 | + || isWord(zIn, "<center", 7) | |
| 1808 | + || (isWord(zIn, "<h", 2) && fossil_isdigit(zIn[2])) | |
| 1809 | + || isWord(zIn, "<p", 2) | |
| 1810 | + || isWord(zIn, "<table", 6) ){ | |
| 1811 | + blob_append(pOut, "\n\n", 1 + (iCur>0)); | |
| 1812 | + iCur = 0; | |
| 1813 | + }else if( isWord(zIn, "<dd", 3) | |
| 1814 | + || isWord(zIn, "<div", 4) | |
| 1815 | + || isWord(zIn, "<dl", 3) | |
| 1816 | + || isWord(zIn, "<dt", 3) | |
| 1817 | + || isWord(zIn, "<li", 3) | |
| 1818 | + || isWord(zIn, "<ol", 3) | |
| 1819 | + || isWord(zIn, "<td", 3) | |
| 1820 | + || isWord(zIn, "<th", 3) | |
| 1821 | + || isWord(zIn, "<tr", 3) | |
| 1822 | + || isWord(zIn, "<ul", 3) ){ | |
| 1823 | + if( iCur>0 ) blob_append(pOut, "\n", 1); | |
| 1824 | + iCur = 0; | |
| 1825 | + } | |
| 1826 | + blob_append(pOut, zIn, n); | |
| 1827 | + iCur += n; | |
| 1828 | + }else if( fossil_isspace(zIn[0]) ){ | |
| 1829 | + if( nPre ){ | |
| 1830 | + blob_append(pOut, zIn, n); | |
| 1831 | + }else if( iCur>=70 ){ | |
| 1832 | + blob_append(pOut, "\n", 1); | |
| 1833 | + iCur = 0; | |
| 1834 | + }else{ | |
| 1835 | + blob_append(pOut, " ", 1); | |
| 1836 | + iCur++; | |
| 1837 | + } | |
| 1838 | + }else{ | |
| 1839 | + blob_append(pOut, zIn, n); | |
| 1840 | + iCur += n; | |
| 1841 | + } | |
| 1842 | + zIn += n; | |
| 1843 | + } | |
| 1844 | + if( iCur ) blob_append(pOut, "\n", 1); | |
| 1845 | +} | |
| 1846 | + | |
| 1847 | +/* | |
| 1848 | +** COMMAND: test-html-tidy | |
| 1849 | +*/ | |
| 1850 | +void test_html_tidy(void){ | |
| 1851 | + Blob in, out; | |
| 1852 | + int i; | |
| 1853 | + | |
| 1854 | + for(i=2; i<g.argc; i++){ | |
| 1855 | + blob_read_from_file(&in, g.argv[i]); | |
| 1856 | + blob_zero(&out); | |
| 1857 | + htmlTidy(blob_str(&in), &out); | |
| 1858 | + blob_reset(&in); | |
| 1859 | + fossil_puts(blob_str(&out), 0); | |
| 1860 | + blob_reset(&out); | |
| 1861 | + } | |
| 1862 | +} | |
| 1752 | 1863 |
| --- src/wikiformat.c | |
| +++ src/wikiformat.c | |
| @@ -1747,5 +1747,116 @@ | |
| 1747 | } |
| 1748 | z += n; |
| 1749 | } |
| 1750 | free(renderer.aStack); |
| 1751 | } |
| 1752 |
| --- src/wikiformat.c | |
| +++ src/wikiformat.c | |
| @@ -1747,5 +1747,116 @@ | |
| 1747 | } |
| 1748 | z += n; |
| 1749 | } |
| 1750 | free(renderer.aStack); |
| 1751 | } |
| 1752 | |
| 1753 | /* |
| 1754 | ** Get the next HTML token. |
| 1755 | ** |
| 1756 | ** z points to the start of a token. Return the number of |
| 1757 | ** characters in that token. |
| 1758 | */ |
| 1759 | static int nextHtmlToken(const char *z){ |
| 1760 | int n; |
| 1761 | if( z[0]=='<' ){ |
| 1762 | n = markupLength(z); |
| 1763 | if( n<=0 ) n = 1; |
| 1764 | }else if( fossil_isspace(z[0]) ){ |
| 1765 | for(n=1; z[n] && fossil_isspace(z[n]); n++){} |
| 1766 | }else{ |
| 1767 | for(n=1; z[n] && z[n]!='<' && !fossil_isspace(z[n]); n++){} |
| 1768 | } |
| 1769 | return n; |
| 1770 | } |
| 1771 | |
| 1772 | /* |
| 1773 | ** Return true if z[] is the word zWord in any case. |
| 1774 | */ |
| 1775 | static int isWord(const char *z, const char *zWord, int nWord){ |
| 1776 | return fossil_strnicmp(z, zWord, nWord)==0 && !fossil_isalpha(z[nWord]); |
| 1777 | } |
| 1778 | |
| 1779 | /* |
| 1780 | ** Attempt to reformat messy HTML to be easily readable by humans. |
| 1781 | ** |
| 1782 | ** * Try to keep lines less than 80 characters in length |
| 1783 | ** * Collapse white space into a single space |
| 1784 | ** * Put a blank line before: |
| 1785 | ** <blockquote><center><code><hN><p><pre><table> |
| 1786 | ** * Put a newline after <br> and <hr> |
| 1787 | ** * Start each of the following elements on a new line: |
| 1788 | ** <address><cite><dd><div><dl><dt><li><ol><samp> |
| 1789 | ** <tbody><td><tfoot><th><thead><tr><ul> |
| 1790 | ** |
| 1791 | ** Except, do not do any reformatting inside of <pre>...</pre> |
| 1792 | */ |
| 1793 | void htmlTidy(const char *zIn, Blob *pOut){ |
| 1794 | int n; |
| 1795 | int nPre = 0; |
| 1796 | int iCur = 0; |
| 1797 | while( zIn[0] ){ |
| 1798 | n = nextHtmlToken(zIn); |
| 1799 | if( zIn[0]=='<' && n>1 ){ |
| 1800 | if( isWord(zIn, "<pre", 4) ){ |
| 1801 | if( iCur && nPre==0 ){ blob_append(pOut, "\n", 1); iCur = 0; } |
| 1802 | nPre++; |
| 1803 | }else if( isWord(zIn, "</pre", 5) ){ |
| 1804 | nPre--; |
| 1805 | if( nPre==0 ){ blob_append(pOut, "\n", 1); iCur = 0; } |
| 1806 | }else if( isWord(zIn, "<blockquote", 11) |
| 1807 | || isWord(zIn, "<center", 7) |
| 1808 | || (isWord(zIn, "<h", 2) && fossil_isdigit(zIn[2])) |
| 1809 | || isWord(zIn, "<p", 2) |
| 1810 | || isWord(zIn, "<table", 6) ){ |
| 1811 | blob_append(pOut, "\n\n", 1 + (iCur>0)); |
| 1812 | iCur = 0; |
| 1813 | }else if( isWord(zIn, "<dd", 3) |
| 1814 | || isWord(zIn, "<div", 4) |
| 1815 | || isWord(zIn, "<dl", 3) |
| 1816 | || isWord(zIn, "<dt", 3) |
| 1817 | || isWord(zIn, "<li", 3) |
| 1818 | || isWord(zIn, "<ol", 3) |
| 1819 | || isWord(zIn, "<td", 3) |
| 1820 | || isWord(zIn, "<th", 3) |
| 1821 | || isWord(zIn, "<tr", 3) |
| 1822 | || isWord(zIn, "<ul", 3) ){ |
| 1823 | if( iCur>0 ) blob_append(pOut, "\n", 1); |
| 1824 | iCur = 0; |
| 1825 | } |
| 1826 | blob_append(pOut, zIn, n); |
| 1827 | iCur += n; |
| 1828 | }else if( fossil_isspace(zIn[0]) ){ |
| 1829 | if( nPre ){ |
| 1830 | blob_append(pOut, zIn, n); |
| 1831 | }else if( iCur>=70 ){ |
| 1832 | blob_append(pOut, "\n", 1); |
| 1833 | iCur = 0; |
| 1834 | }else{ |
| 1835 | blob_append(pOut, " ", 1); |
| 1836 | iCur++; |
| 1837 | } |
| 1838 | }else{ |
| 1839 | blob_append(pOut, zIn, n); |
| 1840 | iCur += n; |
| 1841 | } |
| 1842 | zIn += n; |
| 1843 | } |
| 1844 | if( iCur ) blob_append(pOut, "\n", 1); |
| 1845 | } |
| 1846 | |
| 1847 | /* |
| 1848 | ** COMMAND: test-html-tidy |
| 1849 | */ |
| 1850 | void test_html_tidy(void){ |
| 1851 | Blob in, out; |
| 1852 | int i; |
| 1853 | |
| 1854 | for(i=2; i<g.argc; i++){ |
| 1855 | blob_read_from_file(&in, g.argv[i]); |
| 1856 | blob_zero(&out); |
| 1857 | htmlTidy(blob_str(&in), &out); |
| 1858 | blob_reset(&in); |
| 1859 | fossil_puts(blob_str(&out), 0); |
| 1860 | blob_reset(&out); |
| 1861 | } |
| 1862 | } |
| 1863 |