Edits to make markdown pass John Gruder's test suite.

This commit is contained in:
david parsons
2008-01-09 17:36:55 -08:00
parent 2c3379ed7f
commit 402c0ea525
7 changed files with 300 additions and 88 deletions
+116 -52
View File
@@ -76,6 +76,17 @@ cursor(MMIOT *f)
}
/* return/set the current cursor position
*/
unsigned
mmseek(MMIOT *f, int i)
{
if ( i >= 0 && i < S(f->in) )
f->isp = i;
return f->isp;
}
/* move n characters forward ( or -n characters backward) in the input buffer.
*/
static void
@@ -116,35 +127,52 @@ reparse(char *bfr, int size, int flags, MMIOT *f)
}
/*
* write a character to output, expanding spaces
* and unprintables to %02x format, otherwise
* with putsafec
*/
static void
puturlc(char c, FILE *f)
{
if ( !c )
return;
if ( (c == ' ') || (c == '"') || !isprint(c) )
fprintf(f, "%%%02x", c);
else if ( c == '&' )
fputs("&amp;", f);
else if ( c == '<' )
fputs("&lt;", f);
else
fputc(c, f);
}
/*
* write out a url, escaping problematic characters
*/
static void
puturl(char *s, int size, FILE *f)
{
while ( size-- > 0 )
puturlc(*s++, f);
unsigned char c;
for ( ; size ; --size, ++s ) {
switch (c = *s) {
case '<': fputs("&lt;", f); break;
case '>': fputs("&gt;", f); break;
case '&': fputs("&amp;", f); break;
#if 0
case '&': if ( (size < 5) || strncmp(s, "&amp;", 5) != 0 ) {
fputs("&amp;", f);
break;
}
/*else fall into... */
#endif
default: if ( c == '"' || c == ' ' || !isprint(c) )
fprintf(f, "%%%02x", c);
else
fputc(c, f);
break;
}
}
}
static int
broketmatch(MMIOT *f, int i)
{
int c = peek(f, 1+i);
return (c == '>') || (c == EOF);
}
static int
spacematch(MMIOT *f, int i)
{
int c = peek(f, i);
return (c == EOF) || (c == ')') || isspace(c);
}
@@ -178,22 +206,28 @@ linkylinky(int image, MMIOT *f)
}
shift(f, j);
fprintf(f->out, image ? "<img" : "<a");
switch (c) {
char *tag;
Footnote key, *ret;
int (*matched)(MMIOT*,int);
case '(':
for ( size =1; (c=peek(f, size)) != ')' && !isspace(c); ++size)
if ( c == EOF )
break;
matched = (peek(f,1) == '<') ? broketmatch : spacematch;
fprintf(f->out, " %s=\"", image ? "src" : "href");
puturl(cursor(f), size-1, f->out);
for ( size = 0; !(*matched)(f, size+1); ++size)
;
fprintf(f->out, "<%s %s=\"", image ? "img" : "a",
image ? "src" : "href");
if ( peek(f,1) == '<' ) {
puturl(cursor(f)+1, size, f->out);
shift(f,3); /* shift past '(', '<' && '>' */
}
else
puturl(cursor(f), size, f->out);
fputc('"', f->out);
shift(f, size-1);
shift(f, size);
skipblankc(f);
if ( image && (peek(f,1) == '=') ) {
@@ -212,8 +246,15 @@ linkylinky(int image, MMIOT *f)
}
if ( (qc=peek(f,1)) == '"' || qc == '\'' ) {
for ( size=0; ((c=peek(f,size+2)) != EOF) && (c != qc) ; ++size )
/* title; now run up to the end of the () block, then
* back down to the last quote character we found.
* This allows evil things like "this "is" a "title"")
*/
for ( size=0; ((c=peek(f,size+2)) != EOF) && (c != ')') ; ++size )
;
while ( size && (peek(f,size+2) != qc) )
--size;
fprintf(f->out, " title=\"");
reparse(cursor(f)+1, size, DENY_A|DENY_IMG|EXPAND_QUOTE, f);
fputc('"', f->out);
@@ -246,6 +287,8 @@ linkylinky(int image, MMIOT *f)
ret = bsearch(&key, T(f->footnotes), S(f->footnotes),
sizeof key, (stfu)__mkd_footsort);
fprintf(f->out, image ? "<img" : "<a");
if ( ret ) {
if ( S(ret->link) ) {
fprintf(f->out, " %s=\"", image ? "src" : "href");
@@ -326,7 +369,7 @@ maybe_tag_or_link(MMIOT *f)
if ( size == 0 )
return 0;
if ( maybetag ) {
if ( maybetag || (size >= 3 && strncmp(cursor(f), "!--", 3) == 0) ) {
fprintf(f->out, forbidden_tag(f) ? "&lt;" : "<");
return 1;
}
@@ -334,16 +377,19 @@ maybe_tag_or_link(MMIOT *f)
if ( f->flags & DENY_A ) return 0;
text = cursor(f);
shift(f, size);
shift(f, size+1);
for ( i=0; i < SZAUTOPREFIX; i++ )
if ( strncasecmp(text, autoprefix[i], strlen(autoprefix[i])) == 0 ) {
fprintf(f->out, "&lt;<a href=\"%.*s\">%.*s</a>",
size, text, size, text);
fprintf(f->out, "<a href=\"");
puturl(text,size,f->out);
fprintf(f->out, "\">");
reparse(text, size, 0, f);
fprintf(f->out, "</a>");
return 1;
}
if ( maybeaddress ) {
fprintf(f->out, "&lt;<a href=\"");
fprintf(f->out, "<a href=\"");
mangle("mailto:", 7, f);
mangle(text, size, f);
fprintf(f->out,"\">");
@@ -352,7 +398,7 @@ maybe_tag_or_link(MMIOT *f)
return 1;
}
shift(f, -size);
shift(f, -(size+1));
return 0;
} /* maybe_tag_or_link */
@@ -399,7 +445,7 @@ smartypants(int c, int *flags, MMIOT *f)
switch (c) {
case '\'': if ( (c=toupper(peek(f,1)) == 'S' || c == 'T' )
&& isthisblank(f, 2) ) {
&& isthisblank(f, 2) ) {
/* 's or 't -> contraction or possessive ess. Not
* smart enough
*/
@@ -484,7 +530,7 @@ text(MMIOT *f)
case 0: break;
case '"': if ( f->flags & EXPAND_QUOTE )
fprintf(f->out, "&#%d;", c);
fprintf(f->out, "&quot;", c);
else
fputc(c, f->out);
break;
@@ -547,8 +593,10 @@ text(MMIOT *f)
fprintf(f->out, "&amp;");
else if ( c == '<' )
fprintf(f->out, "&lt;");
#if 0
else if ( c == '>' )
fprintf(f->out, "&gt;");
#endif
else
fputc( c ? c : '\\', f->out);
break;
@@ -587,15 +635,26 @@ code(int escape, MMIOT *f)
while ( (c = pull(f)) != EOF ) {
switch (c) {
case '&': fprintf(f->out, "&amp;"); break;
case '>': fprintf(f->out, "&gt;"); break;
case '<': fprintf(f->out, "&lt;"); break;
case '`': switch (escape) {
case 2: if ( peek(f,1) == '`' ) {
shift(f,1);
case 1: return;
}
}
fputc(c, f->out); break;
default: fputc(c, f->out); break;
case '\\': fputc(c, f->out);
if ( peek(f,1) == '>' || (c = pull(f)) == EOF )
break;
fputc(c, f->out);
break;
}
}
} /* code */
@@ -606,10 +665,10 @@ code(int escape, MMIOT *f)
static void
printheader(Paragraph *pp, MMIOT *f)
{
fprintf(f->out, "<H%d>", pp->hnumber);
fprintf(f->out, "<h%d>", pp->hnumber);
push(T(pp->text->text), S(pp->text->text), f);
text(f);
fprintf(f->out, "</H%d>\n", pp->hnumber);
fprintf(f->out, "</h%d>", pp->hnumber);
}
@@ -618,7 +677,7 @@ printblock(Paragraph *pp, MMIOT *f)
{
Line *t = pp->text;
static char *Begin[] = { "", "<p>", "<center>" };
static char *End[] = { "", "</p>\n","</center>\n"};
static char *End[] = { "", "</p>","</center>"};
while (t) {
if ( S(t->text) ) {
@@ -629,7 +688,8 @@ printblock(Paragraph *pp, MMIOT *f)
}
else {
push(T(t->text), S(t->text), f);
push("\n", 1, f);
if ( t->next )
push("\n", 1, f);
}
}
t = t->next;
@@ -647,7 +707,7 @@ printcode(Line *t, MMIOT *f)
int blanks;
for ( blanks = 0; t ; t = t->next )
if ( S(t->text) ) {
if ( S(t->text) > t->dle ) {
while ( blanks ) {
push("\n", 1, f);
--blanks;
@@ -659,7 +719,7 @@ printcode(Line *t, MMIOT *f)
fprintf(f->out, "<pre><code>");
code(0, f);
fprintf(f->out, "</code></pre>\n");
fprintf(f->out, "</code></pre>");
}
@@ -684,12 +744,14 @@ printhtml(Line *t, MMIOT *f)
static void
emit(Paragraph *p, char *block, MMIOT *f)
{
if ( block ) fprintf(f->out, "<%s>", block);
if ( block )
fprintf(f->out, "<%s>", block);
while (( p = display(p, f) ))
;
fputs("\n\n", f->out);
if ( block ) fprintf(f->out, "</%s>\n", block);
if ( block )
fprintf(f->out, "</%s>", block);
}
@@ -711,7 +773,7 @@ definitionlist(Paragraph *p, MMIOT *f)
emit(p->down, "dd", f);
}
fprintf(f->out, "</dl>\n");
fprintf(f->out, "</dl>");
}
}
#endif
@@ -725,9 +787,10 @@ listdisplay(int typ, Paragraph *p, MMIOT* f)
for ( ; p ; p = p->next ) {
emit(p->down, "li", f);
putc('\n', f->out);
}
fprintf(f->out, "</%cl>\n", (typ==UL)?'u':'o');
fprintf(f->out, "</%cl>", (typ==UL)?'u':'o');
}
}
@@ -767,7 +830,7 @@ display(Paragraph *p, MMIOT *f)
#endif
case HR:
fprintf(f->out, "<HR />\n");
fprintf(f->out, "<hr />");
break;
case HDR:
@@ -788,6 +851,7 @@ void
__mkd_generatehtml(Paragraph *p, MMIOT *f)
{
emit(p, 0, f);
putc('\n', f->out);
}
+7 -1
View File
@@ -1,5 +1,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <unistd.h>
#include <mkdio.h>
#include <errno.h>
@@ -29,9 +30,14 @@ main(int argc, char **argv)
{
int opt;
int rc;
int flags = 0;
int debug = 0;
char *ofile = 0;
extern char version[];
char *q = getenv("MARKDOWN_FLAGS");
if ( q ) flags = strtol(q, 0, 0);
opterr = 1;
@@ -69,6 +75,6 @@ main(int argc, char **argv)
rc = mkd_dump(mkd_in(stdin), stdout, 0,
argc ? basename(argv[0]) : "stdin");
else
rc = markdown(mkd_in(stdin), stdout, 0);
rc = markdown(mkd_in(stdin), stdout, flags);
exit( (rc == 0) ? 0 : errno );
}
+100 -34
View File
@@ -18,11 +18,12 @@
/* block-level tags for passing html blocks through the blender
*/
static char *blocktags[] = { "ADDRESS", "BDO", "BLOCKQUOTE", "CENTER",
"DFN", "DIV", "H1", "H2", "H3", "H4",
"H5", "H6", "LISTING", "NOBR", "UL",
"P", "OL", "DL", "PLAINTEXT", "PRE",
"WBR", "XMP" };
static char *blocktags[] = { "!--",
"ADDRESS", "BDO", "BLOCKQUOTE", "CENTER",
"DFN", "DIV", "H1", "H2", "H3", "H4",
"H5", "H6", "LISTING", "NOBR", "UL",
"P", "OL", "DL", "PLAINTEXT", "PRE",
"WBR", "XMP", "HR", "BR" };
#define SZTAGS (sizeof blocktags / sizeof blocktags[0])
typedef int (*stfu)(const void*,const void*);
@@ -185,8 +186,13 @@ isopentag(Line *p)
if ( len < 3 || T(p->text)[0] != '<' )
return 0;
for (i=1; (i < len) && isalnum(T(p->text)[i]); ++i)
/* skip over tag (don't care about arguments or '>' yet) */ ;
/* find how long the tag is so we can check to see if
* it's a block-level tag
*/
for ( i=1; i < len && T(p->text)[i] != '>'
&& T(p->text)[i] != '/'
&& !isspace(T(p->text)[i]); ++i )
;
key = alloca(i);
memcpy(key, T(p->text)+1, i-1);
@@ -197,38 +203,71 @@ isopentag(Line *p)
static int
isclosetag(Line *p, char *key, int siz)
selfclose(Line *t, char *tag)
{
int eol;
if ( !p ) return 0;
char *q = T(t->text);
int siz = strlen(tag);
int i;
eol = S(p->text)-1;
if ( strcasecmp(tag, "HR") == 0 || strcasecmp(tag, "BR") == 0 )
/* <HR> and <BR> are self-closing block-level tags,
*/
return 1;
if ( T(p->text)[eol] != '>' ) return 0;
if ( strncasecmp(T(p->text)+(eol-siz), key, siz) != 0 ) return 0;
if ( T(p->text)[eol-(siz+1)] != '/' ) return 0;
if ( T(p->text)[eol-(siz+2)] != '<' ) return 0;
return 1;
i = S(t->text) - (siz + 3);
/* we specialcase start and end tags on the same line.
*/
return ( i > 0 ) && (q[i] == '<') && (q[i+1] == '/')
&& (q[i+2+siz] == '>')
&& (strncasecmp(&q[i+2], tag, siz) == 0);
}
static Line *
htmlblock(Paragraph *p, char *key)
htmlblock(Paragraph *p, char *tag)
{
Line *t, *ret;
int keysize = strlen(key);
Line *t = p->text, *ret;
int closesize;
char *close = alloca(strlen(tag)+4);
for ( t = p->text; t ; t = t->next)
if ( isclosetag(t, key, keysize) && blankline(t->next) ) {
sprintf(close, "</%s>", tag);
closesize = strlen(close);
if ( selfclose(t, tag) ) {
ret = t->next;
t->next = 0;
return ret;
}
for ( ; t ; t = t->next) {
if ( strncasecmp(T(t->text), close, closesize) == 0 ) {
ret = t->next;
t->next = 0;
return ret;
}
}
return t;
}
static Line *
comment(Paragraph *p, char *key)
{
Line *t, *ret;
for ( t = p->text; t ; t = t->next) {
if ( strstr(T(t->text), "-->") ) {
ret = t->next;
t->next = 0;
return ret;
}
}
return t;
}
/* footnotes look like ^<whitespace>{0,3}[stuff]: <content>$
*/
static int
@@ -415,19 +454,27 @@ headerblock(Paragraph *pp, int htyp)
case ETX:
/* p->text is ###header###, so we need to trim off
* the leading and trailing `#`'s
* the leading and trailing `#`'s, plus any framing
* whitespace.
*/
for (i=0; T(p->text)[i] == T(p->text)[0]; i++)
;
pp->hnumber = i;
while ( isspace(T(p->text)[i]) )
i++;
CLIP(p->text, 0, i);
for (j=S(p->text); j && (T(p->text)[j-1] == '#'); --j)
;
CLIP(p->text,j, S(p->text)-j);
while ( j && isspace(T(p->text)[j-1]) )
--j;
S(p->text) = j;
ret = p->next;
p->next = 0;
break;
@@ -448,17 +495,20 @@ fancy(Line *t)
static Line *
codeblock(Paragraph *p)
{
Line *t, *r, *ret;
Line *t, *r;
int i;
for ( t = p->text; t; t = r ) {
CLIP(t->text,0,4);
t->dle = mkd_firstnonblank(t);
r = t->next;
if ( r && !iscode(r) ) {
ret = t->next;
for (i=S(t->text); i && isspace(T(t->text)[i-1]); --i)
;
S(t->text) = i;
if ( !( (r = skipempty(t->next)) && iscode(r)) ) {
t->next = 0;
return ret;
return r;
}
}
return t;
@@ -663,8 +713,19 @@ addfootnote(Line *p, MMIOT* f)
}
if ( (c = tgood(T(p->text)[j])) ) {
while ( (j < S(p->text)-1) && (T(p->text)[++j] != c) )
EXPAND(foot->title) = T(p->text)[j];
/* Try to take the rest of the line as a comment; read to
* EOL, then shrink the string back to before the final
* quote.
*/
++j; /* skip leading quote */
while ( j < S(p->text) )
EXPAND(foot->title) = T(p->text)[j++];
while ( S(foot->title) && T(foot->title)[S(foot->title)-1] != c )
--S(foot->title);
if ( S(foot->title) ) /* skip trailing quote */
--S(foot->title);
EXPAND(foot->title) = 0;
}
@@ -693,11 +754,13 @@ static Line*
consume(Line *ptr, int *eaten)
{
Line *next;
int blanks=0;
for ( ; ptr && blankline(ptr); ptr = next, *eaten = 1 ) {
for (; ptr && blankline(ptr); ptr = next, blanks++ ) {
next = ptr->next;
freeLine(ptr);
}
if ( ptr ) *eaten = blanks;
return ptr;
}
@@ -721,7 +784,10 @@ compile(Line *ptr, int toplevel, MMIOT *f)
while ( ptr ) {
if ( toplevel && (key = isopentag(ptr)) ) {
p = Pp(&d, ptr, HTML);
ptr = htmlblock(p, key);
if ( strcmp(key, "!--") == 0 )
ptr = comment(p, key);
else
ptr = htmlblock(p, key);
}
else if ( iscode(ptr) ) {
p = Pp(&d, ptr, CODE);
@@ -742,7 +808,7 @@ compile(Line *ptr, int toplevel, MMIOT *f)
else if ( isquote(ptr) ) {
p = Pp(&d, ptr, QUOTE);
ptr = quoteblock(p);
p->down = compile(p->text, 0, f);
p->down = compile(p->text, 1, f);
p->text = 0;
}
else if ( toplevel && (isfootnote(ptr)) ) {
+1
View File
@@ -8,6 +8,7 @@
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include "cstring.h"
#include "markdown.h"
+73
View File
@@ -0,0 +1,73 @@
echo "html blocks"
rc=0
echo -n ' self-closing block tags (hr) ..... '
SEP='
<hr>
text
'
count=`echo "$SEP" | ./markdown | grep '<p>' | wc -l`
if [ $count -eq 1 ]; then
echo "ok"
else
echo "FAILED"
rc=1
fi
echo -n ' self-closing block tags (hr/) .... '
SEP='
<hr/>
text
'
count=`echo "$SEP" | ./markdown | grep '<p>' | wc -l`
if [ $count -eq 1 ]; then
echo "ok"
else
echo "FAILED"
rc=1
fi
echo -n ' self-closing block tags (br) ..... '
SEP='
<br>
text
'
count=`echo "$SEP" | ./markdown | grep '<p>' | wc -l`
if [ $count -eq 1 ]; then
echo "ok"
else
echo "FAILED"
rc=1
fi
echo -n ' html comments .................... '
SEP='
<!--
**hi**
-->
'
count=`echo "$SEP" | ./markdown | grep 'strong' | wc -l`
if [ $count -eq 0 ]; then
echo "ok"
else
echo "FAILED"
rc=1
fi
exit $rc
+1 -1
View File
@@ -40,7 +40,7 @@ fi
echo -n ' label contains " ................. '
if echo '![he"hehe](url)' | ./markdown | grep -i '&#34;' >/dev/null; then
if echo '![he"hehe](url)' | ./markdown | grep -i '&quot;' >/dev/null; then
echo "ok"
else
echo "FAILED"
+2
View File
@@ -2,6 +2,8 @@ echo "smarty pants"
rc=0
MARKDOWN_FLAGS=0x0; export MARKDOWN_FLAGS
echo -n ' (c) -> &copy; .................... '
if echo '(c)' | ./markdown | grep '&copy;' >/dev/null; then