ADDED src/creoleparser.c Index: src/creoleparser.c ================================================================== --- src/creoleparser.c +++ src/creoleparser.c @@ -0,0 +1,1163 @@ +/* +** Copyright (c) 2009 Robert Ledger +** +** {{{ License +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public +** License version 2 as published by the Free Software Foundation. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** General Public License for more details. +** +** You should have received a copy of the GNU General Public +** License along with this library; if not, write to the +** Free Software Foundation, Inc., 59 Temple Place - Suite 330, +** Boston, MA 02111-1307, USA. +** +** Author contact information: +** robert@pytrash.co.uk +** http://pytrash.co.uk +**}}} +******************************************************************************* +** +** This file contains code to render creole 1.0 formated text as html. +*/ +#include <assert.h> +#include "config.h" +#include "creoleparser.h" + +#if INTERFACE +#define HAVE_CREOLE_MACRO 1 +#endif + +//{{{ LOCAL INTERFACE +#if LOCAL_INTERFACE + +#define POOL_CHUNK_SIZE 100 + +//{{{ KIND +#define KIND_ROOT 0x0000001 +#define KIND_HORIZONTAL_RULE 0x0000002 +#define KIND_HEADING 0x0000004 +#define KIND_ORDERED_LIST 0x0000008 + +#define KIND_UNORDERED_LIST 0x0000010 +#define KIND_PARAGRAPH 0x0000020 +#define KIND_TABLE 0x0000040 +#define KIND_NO_WIKI_BLOCK 0x0000080 + +#define KIND_PARA_BREAK 0x0000100 +#define KIND_END_WIKI_MARKER 0x0000200 + +#define KIND_BOLD 0x0000400 +#define KIND_ITALIC 0x0000800 +#define KIND_SUPERSCRIPT 0x0001000 +#define KIND_SUBSCRIPT 0x0002000 +#define KIND_MONOSPACED 0x0004000 +#define KIND_BREAK 0x0008000 + +#define KIND_TABLE_ROW 0x0010000 +#define KIND_MACRO 0X0020000 +//}}} +//{{{ MACRO +#define MACRO_NONE 0X0000000 +#define MACRO_FOSSIL 0x0000001 +#define MACRO_WIKI_CONTENTS 0X0000002 +//}}} +//{{{ FLAG +// keep first four bits free (why?:) +#define FLAG_CENTER 0x0000100 +#define FLAG_MACRO_BLOCK 0X0000200 +//}}} +struct Node {//{{{ + + char *start; + char *end; + + int kind; + int level; + int flags; + + Node *parent; + Node *next; + Node *children; + +}; +//}}} +struct NodePool {//{{{ + NodePool *next; + Node a[POOL_CHUNK_SIZE]; +} +//}}} +struct Parser {//{{{ + + Blob *pOut; /* Output appended to this blob */ + Renderer *r; + + NodePool *pool; + int nFree; + + Node *this; + Node *previous; + Node *list; + + char *cursor; + + int lineWasBlank; + int charCount; + + Node *item; + Node *istack; + char *icursor; + char *iend; + + int inLink; + int inTable; + int iesc; + + Blob *iblob; + +}; +//}}} + +#endif + +const int KIND_LIST = (KIND_UNORDERED_LIST | KIND_ORDERED_LIST); +const int KIND_LIST_OR_PARAGRAPH = (KIND_PARAGRAPH | KIND_UNORDERED_LIST | KIND_ORDERED_LIST); +//}}} + +//{{{ POOL MANAGEMENT +static Node *pool_new(Parser *p){ + + if ( p->pool == NULL || p->nFree == 0){ + + NodePool *temp = p->pool; + + p->pool = malloc(sizeof(NodePool)); + if( p->pool == NULL ) fossil_panic("out of memory"); + + p->pool->next = temp; + p->nFree = POOL_CHUNK_SIZE; + } + p->nFree -= 1; + Node *node = &(p->pool->a[p->nFree]); + memset(node, 0, sizeof(*node)); + + return node; +} + + +static void pool_free(Parser *p){ + + NodePool *temp; + + while (p->pool != NULL){ + temp = p->pool; + p->pool = temp->next; + free(temp); + } + +} +//}}} + +//{{{ Utility Methods + +static char *cr_skipBlanks(Parser *p, char* z){//{{{ + char *s = z; + while (z[0] == ' ' || z[0] == '\t') z++; + p->charCount = z - s; + return z; +} +//}}} +static int cr_countBlanks(Parser *p, char* z){//{{{ + cr_skipBlanks(p, z); + return p->charCount; +} +//}}} +static char *cr_skipChars(Parser *p, char *z, char c){//{{{ + char *s = z; + while (z[0] == c) z++; + p->charCount = z - s; + return z; +} +//}}} +static int cr_countChars(Parser *p, char *z, char c){//{{{ + cr_skipChars(p, z, c); + return p->charCount; +} +//}}} +static char *cr_nextLine(Parser *p, char *z){//{{{ + + p->lineWasBlank = 1; + + while (1){ + + switch (z[0]){ + + case '\r': + if (z[1] == '\n') { + z[0] = ' '; + return z + 2; + } + z[0] = '\n'; + return z + 1; + + case '\n': + return z + 1; + + case '\t': + z[0] = ' '; + z++; + break; + + case ' ': + z++; + break; + + case '\0': + return z; + + default: + p->lineWasBlank = 0; + z++; + } + } +} +//}}} +//}}} + + +//{{{ INLINE PARSER + +static int cr_isEsc(Parser *p){//{{{ + if (p->iesc){ + blob_append(p->iblob, p->icursor, 1); + p->iesc = 0; + p->icursor += 1; + return 1; + } + return 0; +} +//}}} +static int cr_iOpen(Parser *p, int kind){//{{{ + + switch (kind){ + + case KIND_BOLD: + blob_append(p->iblob, "<strong>", 8); + return 1; + + case KIND_ITALIC: + blob_append(p->iblob, "<em>", 4); + return 1; + + case KIND_SUPERSCRIPT: + blob_append(p->iblob, "<sup>", 5); + return 1; + + case KIND_SUBSCRIPT: + blob_append(p->iblob, "<sub>", 5); + return 1; + + case KIND_MONOSPACED: + blob_append(p->iblob, "<tt>", 4); + return 1; + } + return 0; +} +//}}} +static int cr_iClose(Parser *p, int kind){//{{{ + + switch (kind){ + + case KIND_BOLD: + blob_append(p->iblob, "</strong>", 9); + return 1; + + case KIND_ITALIC: + blob_append(p->iblob, "</em>", 5); + return 1; + + case KIND_SUPERSCRIPT: + blob_append(p->iblob, "</sup>", 6); + return 1; + + case KIND_SUBSCRIPT: + blob_append(p->iblob, "</sub>", 6); + return 1; + + case KIND_MONOSPACED: + blob_append(p->iblob, "</tt>", 5); + return 1; + } + return 0; +} +//}}} + + +static void cr_iMarkup(Parser *p, int kind){//{{{ + + if (p->iesc) { + blob_append(p->iblob, p->icursor, 1); + p->icursor +=1; + p->iesc =0; + return; + } + + if (p->icursor[1] != p->icursor[0]) { + blob_append(p->iblob, p->icursor, 1); + p->icursor +=1; + return; + } + + p->icursor += 2; + + if (kind & KIND_BREAK) { + blob_append(p->iblob, "<br />", 6); + return; + } + + if (kind & KIND_ITALIC && p->icursor[-3] == ':'){ + blob_append(p->iblob, "//", 2); + return; + } + + Node *n = p->istack; + + int found = 0; + while (n) { + if (n->kind & kind) { + found = 1; + break; + } + n = n->next; + } + + if (!found) { + n = pool_new(p); + n->kind = kind; + n->next = p->istack; + p->istack = n; + + assert(cr_iOpen(p, kind)); + return; + }; + + n= p->istack; + while (n){ + p->istack = n->next; + + assert(cr_iClose(p, n->kind)); + + if (kind == n->kind) return; + n = p->istack; + } +} +//}}} +static int cr_iNoWiki(Parser *p){//{{{ + + if ((p->iend - p->icursor)<6) return 0; + + if (p->icursor[1]!='{' || p->icursor[2]!='{') + return 0; + + char *s = p->icursor + 3; + + int count = p->iend - p->icursor - 3; + while (count--){ + if (s[0]=='}' && s[1]=='}' && s[2]=='}' && s[3]!='}'){ + blob_appendf(p->iblob, "<tt class='creole-inline-nowiki'>%s</tt>", htmlize(p->icursor + 3, s - p->icursor-3)); + p->icursor = s + 3; + return 1; + } + s++; + } + return 0; +} + +//}}} +static int cr_iImage(Parser *p){//{{{ + + if (p->inLink) return 0; + if ((p->iend - p->icursor)<3) return 0; + + if (p->icursor[1]!='{') return 0; + + char *s = p->icursor + 2; + char *bar = NULL; + + int count = p->iend - p->icursor - 4; + while (count--){ + if (s[0]=='}' && s[1]=='}'){ + if (!bar) bar = p->icursor + 2; + blob_appendf(p->iblob, "<span class='creole-noimage'>%s</span>", htmlize(bar, s - bar )); + p->icursor = s + 2; + return 1; + } + if (!bar && s[0]=='|') bar=s+1; + s++; + } + return 0; +} +//}}} +static int cr_iMacro(Parser *p){//{{{ + + if (p->inLink) return 0; + if ((p->iend - p->icursor)<3) return 0; + + if (p->icursor[1]!='<') return 0; + + char *s = p->icursor + 2; + + int count = p->iend - p->icursor - 3; + while (count--){ + if (s[0]=='>' && s[1]=='>'){ + blob_appendf(p->iblob, "<span class='creole-nomacro'>%s</span>", htmlize(p->icursor, s - p->icursor + 2)); + p->icursor = s + 2; + return 1; + } + s++; + } + return 0; + +} +//}}} + +static void cr_renderLink(Parser *p, char *s, char *bar, char *e){//{{{ + + int tsize = bar-s; + int dsize = e - bar-1; + + if (tsize < 1) return; + if (dsize < 1) dsize = 0; + + char zTarget[tsize + 1]; + memcpy(zTarget, s, tsize); + zTarget[tsize] = '\0'; + + char zClose[20]; + + Blob *pOut = p->r->pOut; + + p->r->pOut = p->iblob; + wf_openHyperlink(p->r, zTarget, zClose, sizeof(zClose)); + p->r->pOut = pOut; + + if (dsize) + cr_parseInline(p, bar+1, e) ; + else + blob_append(p->iblob, htmlize(s, tsize), -1); + blob_append(p->iblob, zClose, -1); +} +//}}} + +static int cr_iLink(Parser *p){//{{{ + + if (p->inLink) return 0; + if ((p->iend - p->icursor)<3) return 0; + + if (p->icursor[1]!='[') return 0; + + char *s = p->icursor + 2; + char *bar = NULL; + + int count = p->iend - p->icursor -3; + while (count--){ + if (s[0]==']' && s[1]==']'){ + if (!bar) bar = s; + p->inLink = 1; + cr_renderLink(p, p->icursor+2, bar, s); + p->inLink = 0; + p->icursor = s + 2; + return 1; + } + if (!bar && s[0]=='|') bar=s; + s++; + } + return 0; +} +//}}} + +LOCAL char *cr_parseInline(Parser *p, char *s, char *e){//{{{ + + int save_iesc = p->iesc; + char *save_iend = p->iend; + Node *save_istack = p->istack; + + p->iesc = 0; + p->iend = e; + p->istack = NULL; + + p->icursor = s; + + char *eof = NULL; + while (!eof && p->icursor < p->iend ){ + + switch (*p->icursor) {//{{{ + + case '~': + if (p->iesc) { + blob_append(p->iblob, "~", 1); + p->iesc = 0; + } + p->iesc = !p->iesc; + p->icursor+=1; + break; + + case '*': + cr_iMarkup(p, KIND_BOLD); + break; + + case '/': + cr_iMarkup(p, KIND_ITALIC); + break; + + case '^': + cr_iMarkup(p, KIND_SUPERSCRIPT); + break; + + case ',': + cr_iMarkup(p, KIND_SUBSCRIPT); + break; + + case '#': + cr_iMarkup(p, KIND_MONOSPACED); + break; + + case '\\': + cr_iMarkup(p, KIND_BREAK); + break; + + case '{': + if (cr_isEsc(p)) break; + if (cr_iNoWiki(p)) break; + if (cr_iImage(p)) break; + blob_append(p->iblob, p->icursor, 1); + p->icursor += 1; + break; + + case '[': + if (cr_isEsc(p)) break; + if (cr_iLink(p)) break; + blob_append(p->iblob, p->icursor, 1); + p->icursor += 1; + break; + + + case '<': + if (cr_isEsc(p)) break; + if (cr_iMacro(p)) break; + + blob_append(p->iblob, "<", 4); + p->icursor += 1; + break; + + case '>': + if (p->iesc) { + blob_append(p->iblob, "~", 1); + p->iesc = 0; + } + blob_append(p->iblob, ">", 4); + p->icursor += 1; + break; + + case '&': + if (p->iesc) { + blob_append(p->iblob, "~", 1); + p->iesc = 0; + } + blob_append(p->iblob, "&", 5); + p->icursor += 1; + break; + + case '|': + if (p->inTable){ + if (p->iesc) { + blob_append(p->iblob, p->icursor, 1); + p->iesc = 0; + p->icursor += 1; + break; + } + eof = p->icursor + 1; + break; + } + // fall through to default + + default: + if (p->iesc) { + blob_append(p->iblob, "~", 1); + p->iesc = 0; + } + blob_append(p->iblob, p->icursor, 1); + p->icursor +=1; + }//}}} + + } + + while (p->istack){ + cr_iClose(p, p->istack->kind); + p->istack = p->istack->next; + } + + p->iesc = save_iesc; + p->iend = save_iend; + p->istack = save_istack; + + return eof; + +} +//}}} +//}}} + +//{{{ BLOCK PARSER + +static void cr_renderListItem(Parser *p, Node *n){//{{{ + + + blob_append(p->iblob, "<li>", 4); + cr_parseInline(p, n->start, n->end); + + if (n->children){ + + int ord = (n->children->kind & KIND_ORDERED_LIST); + + if (ord) blob_append(p->iblob, "<ol>", 4); + else blob_append(p->iblob, "<ul>", 4); + + n = n->children; + while (n){ + cr_renderListItem(p, n); + n = n->next; + } + + if (ord) blob_append(p->iblob, "</ol>", 5); + else blob_append(p->iblob, "</ul>", 5); + } + blob_append(p->iblob, "</li>", 5); +} +//}}} +static void cr_renderList(Parser *p){//{{{ + + Node *n = p->list; + + while (n->parent !=n) n = n->parent; + + int ord = (n->kind & KIND_ORDERED_LIST); + + if (ord) blob_append(p->iblob, "\n\n<ol>", -1); + else blob_append(p->iblob, "\n\n<ul>", -1); + + while (n) { + cr_renderListItem(p, n); + n = n->next; + } + + if (ord) blob_append(p->iblob, "</ol>", 5); + else blob_append(p->iblob, "</ul>", 5); +} + +//}}} + +static void cr_renderTableRow(Parser *p, Node *row){//{{{ + + char *s = row->start; + int th; + + blob_append(p->iblob, "\n<tr>", -1); + + while (s && s < row->end){ + + if ((th = *s == '=')) { + s++; + blob_append(p->iblob, "<th>", -1); + } + else { + blob_append(p->iblob, "<td>", -1); + } + + s = cr_parseInline(p, s, row->end); + + if (th) + blob_append(p->iblob, "</th>\n", -1); + else + blob_append(p->iblob, "</td>\n", -1); + + if (!s) break; + } + blob_append(p->iblob, "</tr>", 5); +} +//}}} +static void cr_renderTable(Parser *p, Node *n){//{{{ + + Node *row = n->children; + + blob_append(p->iblob, "<table class='creoletable'>", -1); + p->inTable = 1; + while (row){ + + cr_renderTableRow(p, row); + row = row->next; + + } + blob_append(p->iblob, "</table>", -1); + p->inTable = 0; + +} +//}}} + +static void cr_renderMacro(Parser *p, Node *n){//{{{ + + switch (n->level){ + + case MACRO_WIKI_CONTENTS: + do_macro_wiki_contents(p, n); + break; + + } + +} +//}}} + +static void cr_render(Parser *p, Node *node){//{{{ + + if (node->kind & KIND_PARAGRAPH){ + blob_append(p->iblob, "\n<p>", -1); + cr_parseInline(p, node->start, node->end ); + blob_append(p->iblob, "</p>\n", -1 ); + } + + if (node->kind & KIND_HEADING){ + blob_appendf(p->iblob, + "\n<h%d %s>", + node->level, + (node->flags & FLAG_CENTER) ? " style='text-align:center;'" : "" + ); + cr_parseInline(p, node->start, node->end); + blob_appendf(p->iblob, "</h%d>\n", node->level ); + return; + } + + if (node->kind & KIND_MACRO){ + cr_renderMacro(p, node); + return; + } + + if (node->kind & KIND_HORIZONTAL_RULE){ + blob_append(p->iblob, "<hr />", -1); + return; + } + + if (node->kind & KIND_LIST){ + cr_renderList(p); + p->list = NULL; + return; + } + + if (node->kind & KIND_TABLE){ + cr_renderTable(p, node); + return; + } + + if (node->kind & KIND_NO_WIKI_BLOCK){ + blob_appendf(p->iblob, + "\n<pre class='creole-block-nowiki'>%s</pre>\n", + htmlize( node->start, node->end - node->start) + ); + } +} +//}}} + +static char *cr_findEndOfBlock(Parser *p, char *s, char c){//{{{ + + char *end; + while (s[0]){ + + end = s; + if (s[0] == c && s[0] == c && s[0] == c) { + s = cr_nextLine(p, s + 3); + if (p->lineWasBlank) { + p->cursor = s; + return end; + } + } + else { + s = cr_nextLine(p, s); + } + } + return 0; +} +//}}} +static int cr_addListItem(Parser *p, Node *n){//{{{ + + n->parent = n; + n->next = n->children = NULL; + + if (!p->list) { + if (n->level != 1) return 0; + p->list = n; + return 1; + } + + Node *list = p->list; + + while (n->level < list->level){ + list = list->parent; + } + + if (n->level == list->level){ + + if (n->kind != list->kind){ + if (n->level>1) return 0; + cr_renderList(p); + p->list = n; + return 1; + } + n->parent = list->parent; + p->list = list->next = n; + return 1; + } + + if ( (n->level - list->level) > 1 ) return 0; + n->parent = p->list; + p->list->children = n; + p->list = n; + return 1; + +} +//}}} + +static int isEndWikiMarker(Parser *p){//{{{ + + char *s = p->cursor; + if (memcmp(s, "<<fossil>>", 10)) return 0; + p->this->start = s; + p->this->kind = KIND_END_WIKI_MARKER; + p->cursor += 10; + return 1; +} +//}}} +static int isNoWikiBlock(Parser *p){//{{{ + + char *s = p->cursor; + + if (s[0] != '{') return 0; s++; + if (s[0] != '{') return 0; s++; + if (s[0] != '{') return 0; s++; + + s = cr_nextLine(p, s); + if (!p->lineWasBlank) return 0; + + p->this->start = s; + + s = cr_findEndOfBlock(p, s, '}'); + + if (!s) return 0; + + // p->cursor was set by findEndOfBlock + p->this->kind = KIND_NO_WIKI_BLOCK; + p->this->end = s; + return 1; +} + +//}}} +static int isParaBreak(Parser *p){//{{{ + + char *s = cr_nextLine(p, p->cursor); + if (!p->lineWasBlank) return 0; + + p->cursor = s; + p->this->kind = KIND_PARA_BREAK; + return 1; +} +//}}} +static int isMacro(Parser *p){//{{{ + + char *s = p->cursor; + int macroId; + int matchLength; + + /* This is succinct but somewhat hard to follow. + Read as: If not '<' then return, otherwise increment s; + Repeat above; + If '<' then return; + */ + if (s[0]!='<') return 0; s++; + if (s[0]!='<') return 0; s++; + if (s[0]=='<') return 0; + + matchLength = cr_has_macro(s, ¯oId); + if (!matchLength) return 0; + + s += matchLength; + p->this->start = s; + + if (s[-1]!='>'){ + while (s[0] && s[1] && s[0]!='\n' && !(s[0]=='>' && s[1]=='>')) s++; + if (!(s[0] == '>' && s[1] == '>')) return 0; + s +=2; + } + p->cursor = s; + p->this->kind = KIND_MACRO; + p->this->level = macroId; + p->this->flags &= FLAG_MACRO_BLOCK; + p->this->end = s-2; + return 1; +} +//}}} +static int isHeading(Parser *p){//{{{ + + char *s = cr_skipBlanks(p, p->cursor); + + int flags = 0; + int level = cr_countChars(p, s, '='); + if (!level) return 0; + + s += level; + + if (s[0] == '<' && s[1] == '>') { + flags |= FLAG_CENTER; + s += 2; + } + s = cr_skipBlanks(p, s); + + p->this->start = s; + + s = cr_nextLine(p, s); + char *z = s; + + if (s[-1] == '\n') s--; + while(s[-1] == ' ' || s[-1]=='\t') s--; + while(s[-1] == '=' ) s--; + if (p->this->start < s){ + p->cursor = z; + p->this->kind = KIND_HEADING; + p->this->end = s; + p->this->level = level; + p->this->flags |= flags; + return 1; + } + return 0; +} +//}}} +static int isHorizontalRule(Parser *p){//{{{ + + char *s = cr_skipBlanks(p, p->cursor); + + int level = cr_countChars(p, s, '-'); + + if (level < 4) return 0; + s = cr_nextLine(p, s + level); + if (!p->lineWasBlank) return 0; + + p->cursor = s; + p->this->kind = KIND_HORIZONTAL_RULE; + + return 1; +} +//}}} +static int isListItem(Parser *p){//{{{ + + char *s = cr_skipBlanks(p, p->cursor); + + int level = cr_countChars(p, s, '#'); + if (!level) level = cr_countChars(p, s, '*'); + + if ( !level) return 0; + + p->this->kind = (s[0] == '#') ? KIND_ORDERED_LIST : KIND_UNORDERED_LIST; + p->this->level = level; + + s = cr_skipBlanks(p, s + level); + p->this->start = s; + + s = cr_nextLine(p, s); + if (p->lineWasBlank) return 0; + + if (cr_addListItem(p, p->this)){ + p->cursor = p->this->end = s; + return 1; + } + p->this->kind = 0; + return 0; +} +//}}} +static int isTable(Parser *p){//{{{ + + p->this->start = p->cursor; + char *s = cr_skipBlanks(p, p->cursor); + if (s[0] != '|') return 0; + s +=1; + p->this->kind = KIND_TABLE; + + + //p->cursor = p->this->end = cr_nextLine(p, s); + Node *row; + Node *tail = NULL; + + while (1) { + + row = pool_new(p); + row->kind = KIND_TABLE_ROW; + + if (tail) tail = tail->next = row; + else p->this->children = tail = row; + + row->start = s; + p->cursor = s = row->end = p->this->end = cr_nextLine(p, s); + + if (row->end[-1] == '\n') row->end -= 1; + while(row->end[-1] == ' ' ) row->end -= 1; + if (row->end[-1] == '|') row->end -= 1; + + if (!*s) break; + + // blanks *not* normalized + s = cr_skipBlanks(p, p->cursor); + if (s[0] != '|') break; + s++; + + } + return 1; + +}; +//}}} +static int isParagraph(Parser *p){//{{{ + + char *s = p->cursor; + p->this->start = s; + + s = cr_nextLine(p, s); + p->cursor = p->this->end = s; + p->this->kind = KIND_PARAGRAPH; + return 1; + +} +//}}} + +static void cr_parse(Parser *p, char* z){//{{{ + + p->previous = pool_new(p); + p->previous->kind = KIND_PARA_BREAK; + + p->this = pool_new(p); + p->this->kind = KIND_PARA_BREAK; + + p->inLink = 0; + p->inTable = 0; + + p->cursor = z; + p->list = NULL; + p->istack = NULL; + + while (p->cursor[0]) { + + while (1){ + + // must be first + if (isNoWikiBlock(p)) break; + if (isParaBreak(p)) break; + + // order not important + if (isMacro(p)) break; + if (isHeading(p)) break; + if (isHorizontalRule(p)) break; + if (isListItem(p)) break; + if (isTable(p)) break; + + // here for efficiency? + if (isEndWikiMarker(p)) break; + + // must be last + if (isParagraph(p)); break; + + // doh! + assert(0); + } + + int kind = p->this->kind; + int prev = p->previous->kind; + + if (kind & KIND_END_WIKI_MARKER) return; + + if (kind == KIND_PARAGRAPH && prev & KIND_LIST_OR_PARAGRAPH) { + p->previous->end = p->this->end; + p->this = pool_new(p); + continue; + } + + if ( !(kind & KIND_LIST && prev & KIND_LIST) ) + cr_render(p, p->previous); + + p->previous = p->this; + p->this = pool_new(p); + + } +} +//}}} + +//}}} + +//{{{ MACROS +LOCAL void do_macro_wiki_contents(Parser *p, Node *n){//{{{ + + Stmt q; + + blob_append(p->iblob, "<ul>", 4); + + db_prepare(&q, + "SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname GLOB 'wiki-*'" + " ORDER BY lower(tagname)" + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zName = db_column_text(&q, 0); + blob_appendf(p->iblob, "<li><a href=\"%s/wiki?name=%T\">%h</a></li>", g.zBaseURL, zName, zName); + } + db_finalize(&q); + blob_append(p->iblob, "</ul>", 5); +}//}}} + + +static int cr_match(char *z1, char *z2, int *len){ + *len = strlen(z2); + return !memcmp(z1, z2 ,*len); +} + +int cr_has_macro(char *z, int *tokenType){ + + int len; + + if (cr_match(z, "wiki-contents>>", &len)) { + *tokenType = MACRO_WIKI_CONTENTS; + return len; + } + + tokenType = MACRO_NONE; + return 0; + +} +//}}} + +char *wiki_render_creole(Renderer *r, char *z){ + + Parser parser; + Parser *p = &parser; + + p->r = r; + p->iblob = r->pOut; + + p->nFree = 0; + p->pool = NULL; + + cr_parse(p, z); + + cr_render(p, p->previous); + + pool_free(p); + + return p->cursor; + +} + Index: src/main.mk ================================================================== --- src/main.mk +++ src/main.mk @@ -27,10 +27,11 @@ $(SRCDIR)/clone.c \ $(SRCDIR)/comformat.c \ $(SRCDIR)/configure.c \ $(SRCDIR)/construct.c \ $(SRCDIR)/content.c \ + $(SRCDIR)/creoleparser.c \ $(SRCDIR)/db.c \ $(SRCDIR)/delta.c \ $(SRCDIR)/deltacmd.c \ $(SRCDIR)/descendants.c \ $(SRCDIR)/diff.c \ @@ -100,10 +101,11 @@ clone_.c \ comformat_.c \ configure_.c \ construct_.c \ content_.c \ + creoleparser_.c \ db_.c \ delta_.c \ deltacmd_.c \ descendants_.c \ diff_.c \ @@ -173,10 +175,11 @@ $(OBJDIR)/clone.o \ $(OBJDIR)/comformat.o \ $(OBJDIR)/configure.o \ $(OBJDIR)/construct.o \ $(OBJDIR)/content.o \ + $(OBJDIR)/creoleparser.o \ $(OBJDIR)/db.o \ $(OBJDIR)/delta.o \ $(OBJDIR)/deltacmd.o \ $(OBJDIR)/descendants.o \ $(OBJDIR)/diff.o \ @@ -273,16 +276,16 @@ # noop clean: rm -f $(OBJDIR)/*.o *_.c $(APPNAME) VERSION.h rm -f translate makeheaders mkindex page_index.h headers - rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h construct.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h graph.h http.h http_socket.h http_ssl.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h rstats.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h + rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h construct.h content.h creoleparser.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h graph.h http.h http_socket.h http_ssl.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h rstats.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h page_index.h: $(TRANS_SRC) mkindex ./mkindex $(TRANS_SRC) >$@ headers: page_index.h makeheaders VERSION.h - ./makeheaders add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h graph_.c:graph.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h rstats_.c:rstats.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h + ./makeheaders add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h construct_.c:construct.h content_.c:content.h creoleparser_.c:creoleparser.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h graph_.c:graph.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h rstats_.c:rstats.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h touch headers headers: Makefile Makefile: add_.c: $(SRCDIR)/add.c translate ./translate $(SRCDIR)/add.c >add_.c @@ -394,10 +397,17 @@ $(OBJDIR)/content.o: content_.c content.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/content.o -c content_.c content.h: headers +creoleparser_.c: $(SRCDIR)/creoleparser.c translate + ./translate $(SRCDIR)/creoleparser.c >creoleparser_.c + +$(OBJDIR)/creoleparser.o: creoleparser_.c creoleparser.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/creoleparser.o -c creoleparser_.c + +creoleparser.h: headers db_.c: $(SRCDIR)/db.c translate ./translate $(SRCDIR)/db.c >db_.c $(OBJDIR)/db.o: db_.c db.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/db.o -c db_.c Index: src/makemake.tcl ================================================================== --- src/makemake.tcl +++ src/makemake.tcl @@ -21,10 +21,11 @@ clone comformat configure construct content + creoleparser db delta deltacmd descendants diff Index: src/style.c ================================================================== --- src/style.c +++ src/style.c @@ -383,10 +383,64 @@ @ table.label-value th { @ vertical-align: top; @ text-align: right; @ padding: 0.2ex 2ex; @ } +@ +@ /* For marking important UI elements which shouldn't be +@ lightly dismissed. I mainly use it to mark "not yet +@ implemented" parts of a page. Whether or not to have +@ a 'border' attribute set is arguable. */ +@ .achtung { +@ color: #ff0000; +@ background: #ffff00; +@ border: 1px solid #ff0000; +@ } +@ +@ div.miniform { +@ font-size: smaller; +@ margin: 8px; +@ } +@ /* additions to support creole parser */ +@ +@ .creoletable { +@ border: 1px solid #666666; +@ border-spacing: 0; +@ margin: 1.5em 2em 1.8em 2em; +@ } +@ .creoletable * tr th { +@ font-size: 100%; +@ padding: .5em .7em .5em .7em; +@ border-left: 1px solid #666666; +@ background-color: #558195; +@ vertical-align: bottom; +@ color: white; +@ empty-cells: show; +@ } +@ .creoletable * tr td { +@ padding: .4em .7em .45em .7em; +@ border-left: 1px solid #D9D9D9; +@ border-top: 1px solid #D9D9D9; +@ vertical-align: center; +@ empty-cells: show; +@ } +@ .creole-block-nowiki { +@ background: oldlace; +@ margin: 2em; +@ overflow: auto; +@ } +@ .creole-inline-nowiki { +@ background: oldlace; +@ } +@ .creole-noimage { +@ color:green; +@ border:1px solid green; +@ } +@ .creole-nomacro { +@ color:red; +@ border:1px solid red; +@ } ; /* ** WEBPAGE: style.css */ Index: src/wiki.c ================================================================== --- src/wiki.c +++ src/wiki.c @@ -379,19 +379,20 @@ const char *zRemark; char *zId; zDate = db_text(0, "SELECT datetime('now')"); zId = db_text(0, "SELECT lower(hex(randomblob(8)))"); + blob_append(p, "\n<<fossil>>\n", -1); blob_appendf(p, "\n\n<hr><div id=\"%s\"><i>On %s UTC %h", zId, zDate, g.zLogin); free(zDate); zUser = PD("u",g.zLogin); if( zUser[0] && strcmp(zUser,g.zLogin) ){ blob_appendf(p, " (claiming to be %h)", zUser); } zRemark = PD("r",""); - blob_appendf(p, " added:</i><br />\n%s</div id=\"%s\">", zRemark, zId); + blob_appendf(p, " added:</i><br />\n%s\n<<fossil>>\n</div id=\"%s\">", zRemark, zId); } /* ** WEBPAGE: wikiappend ** URL: /wikiappend?name=PAGENAME Index: src/wikiformat.c ================================================================== --- src/wikiformat.c +++ src/wikiformat.c @@ -335,10 +335,13 @@ #define TOKEN_NUM_LI 7 /* " # " */ #define TOKEN_ENUM 8 /* " \(?\d+[.)]? " */ #define TOKEN_INDENT 9 /* " " */ #define TOKEN_RAW 10 /* Output exactly (used when wiki-use-html==1) */ #define TOKEN_TEXT 11 /* None of the above */ +/* macro tokens - hackyyyy */ +#define TOKEN_FOSSIL 12 /* fossil macro */ +#define TOKEN_CREOLE 13 /* creole macro */ /* ** State flags */ #define AT_NEWLINE 0x001 /* At start of a line */ @@ -350,10 +353,11 @@ #define WIKI_USE_HTML 0x040 /* wiki-use-html option = on */ /* ** Current state of the rendering engine */ +#if INTERFACE typedef struct Renderer Renderer; struct Renderer { Blob *pOut; /* Output appended to this blob */ int state; /* Flag that govern rendering */ int wikiList; /* Current wiki list type */ @@ -368,11 +372,11 @@ short iCode; /* Markup code */ short allowWiki; /* ALLOW_WIKI if wiki allowed before tag */ const char *zId; /* ID attribute or NULL */ } *aStack; }; - +#endif /* ** z points to a "<" character. Check to see if this is the start of ** a valid markup. If it is, return the total number of characters in ** the markup including the initial "<" and the terminating ">". If @@ -556,11 +560,23 @@ ** z points to the start of a token. Return the number of ** characters in that token. Write the token type into *pTokenType. */ static int nextWikiToken(const char *z, Renderer *p, int *pTokenType){ int n; - if( z[0]=='<' ){ + if( !p->inVerbatim && z[0] == '<' && z[1] == '<' ) { + if (!memcmp(z, "<<fossil>>", 10)) { + *pTokenType = TOKEN_FOSSIL; + return 10; + } +#ifdef HAVE_CREOLE_MACRO + if (!memcmp(z, "<<creole>>", 10)) { + *pTokenType = TOKEN_CREOLE; + return 10; + } +#endif + } + else if( z[0]=='<' ){ n = markupLength(z); if( n>0 ){ *pTokenType = TOKEN_MARKUP; return n; }else{ @@ -1067,10 +1083,32 @@ if( wikiUseHtml ){ n = nextRawToken(z, p, &tokenType); }else{ n = nextWikiToken(z, p, &tokenType); } + + /* + ** Additions to support creole switch + */ + if(tokenType == TOKEN_FOSSIL) { + z+=10; + continue; + } +#ifdef HAVE_CREOLE_MACRO + else if(tokenType == TOKEN_CREOLE) { + z = wiki_render_creole(p, z+n); + } +#endif + + /* + if (!p->inVerbatim && z[0]=='<' && z[1] == '<') { + z = wiki_render_macro(p, z, &tokenType); + if (tokenType) continue; + } + */ + /* end additions */ + p->state &= ~(AT_NEWLINE|AT_PARAGRAPH); switch( tokenType ){ case TOKEN_PARAGRAPH: { if( inlineOnly ){ /* blob_append(p->pOut, " ¶ ", -1); */ @@ -1435,5 +1473,45 @@ if( z[i]!='<' ) return 0; blob_init(pTitle, &z[iStart], i-iStart); blob_init(pTail, &z[i+8], -1); return 1; } + +/* +** Additions to support macro extensions +** +** This only allows block level macros, not inline macros +** +** All macros must recognize '<<fossil>>' in the input +** stream and return control to fossil. +*/ + +char *wiki_render_macro(Renderer *p, char *z, int *tokenType){ + if (!memcmp(z, "<<fossil>>", 10)){ + *tokenType = TOKEN_MARKUP; /* Close enough for "in-progress". + Should have it's own token type. */ + return z + 10; + } + +#ifdef HAVE_CREOLE_MACRO + if (!memcmp(z, "<<creole>>", 10)) { + *tokenType = TOKEN_MARKUP; + return wiki_render_creole(p, z+10); + } +#endif + + *tokenType = 0; + return z; +} + +int wf_linkLength(const char *z){ + return linkLength(z); +} +void wf_openHyperlink( + Renderer *p, /* Rendering context */ + const char *zTarget, /* Hyperlink traget; text within [...] */ + char *zClose, /* Write hyperlink closing text here */ + int nClose /* Bytes available in zClose[] */ +){ + return openHyperlink(p, zTarget, zClose, nClose); +} +/* end additions */