Check-in [e01aa8cb4b]
Not logged in
Overview

SHA1 Hash:e01aa8cb4b23e512b16f6d0bd05646b578e5531e
Date: 2008-07-17 15:49:24
User: drh
Comment:Enhance the wiki rendering to support <div> markup. The closing </div> tag with and id= attribute can close off <verbatim> and <nowiki>. The wiki append page uses this to prevent unclosed tags from messing up the rendering of subsequent comments.
Timelines: ancestors | descendants | both | trunk
Other Links: files | ZIP archive | manifest

Tags And Properties
Changes
[hide diffs]

Modified src/wiki.c from [185275fa54] to [26fbd5a93f].

@@ -292,11 +292,11 @@
   }
   for(n=2, z=zBody; z[0]; z++){
     if( z[0]=='\n' ) n++;
   }
   if( n<20 ) n = 20;
-  if( n>200 ) n = 200;
+  if( n>40 ) n = 40;
   @ <form method="POST" action="%s(g.zBaseURL)/wikiedit">
   @ <input type="hidden" name="name" value="%h(zPageName)">
   @ <textarea name="w" class="wikiedit" cols="80"
   @  rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
   @ <br>
@@ -315,20 +315,23 @@
 */
 static void appendRemark(Blob *p){
   char *zDate;
   const char *zUser;
   const char *zRemark;
+  char *zId;
 
   zDate = db_text(0, "SELECT datetime('now')");
-  blob_appendf(p, "\n\n<hr><i>On %s UTC %h", zDate, g.zLogin);
+  zId = db_text(0, "SELECT lower(hex(randomblob(8)))");
+  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", zRemark);
+  blob_appendf(p, " added:</i><br />\n%s</div id=\"%s\">", zRemark, zId);
 }
 
 /*
 ** WEBPAGE: wikiappend
 ** URL: /wikiappend?name=PAGENAME
@@ -352,11 +355,11 @@
       " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
       " ORDER BY mtime DESC", zTag
     );
     free(zTag);
     if( !rid ){
-      cgi_redirect("index");
+      fossil_redirect_home();
       return;
     }
   }
   if( !g.okApndWiki ){
     login_needed();
@@ -378,11 +381,11 @@
       db_set("sandbox", blob_str(&body), 0);
     }else{
       content_get(rid, &content);
       manifest_parse(&m, &content);
       if( m.type==CFTYPE_WIKI ){
-        blob_appendf(&body, m.zWiki, -1);
+        blob_append(&body, m.zWiki, -1);
       }
       manifest_clear(&m);
       blob_zero(&wiki);
       db_begin_transaction();
       zDate = db_text(0, "SELECT datetime('now')");
@@ -395,11 +398,10 @@
         free(zUuid);
       }
       if( g.zLogin ){
         blob_appendf(&wiki, "U %F\n", g.zLogin);
       }
-      blob_appendf(&body, "\n<hr>\n");
       appendRemark(&body);
       blob_appendf(&wiki, "W %d\n%s\n", blob_size(&body), blob_str(&body));
       md5sum_blob(&wiki, &cksum);
       blob_appendf(&wiki, "Z %b\n", &cksum);
       blob_reset(&cksum);
@@ -555,10 +557,11 @@
   @ &lt;center&gt;
   @ &lt;cite&gt;
   @ &lt;code&gt;
   @ &lt;dd&gt;
   @ &lt;dfn&gt;
+  @ &lt;div&gt;
   @ &lt;dl&gt;
   @ &lt;dt&gt;
   @ &lt;em&gt;
   @ &lt;font&gt;
   @ &lt;h1&gt;

Modified src/wikiformat.c from [e49b2821f4] to [55aa7e0f7e].

@@ -39,66 +39,91 @@
 
 
 /*
 ** These are the only markup attributes allowed.
 */
-#define ATTR_ALIGN              0x0000001
-#define ATTR_ALT                0x0000002
-#define ATTR_BGCOLOR            0x0000004
-#define ATTR_BORDER             0x0000008
-#define ATTR_CELLPADDING        0x0000010
-#define ATTR_CELLSPACING        0x0000020
-#define ATTR_CLEAR              0x0000040
-#define ATTR_COLOR              0x0000080
-#define ATTR_COLSPAN            0x0000100
-#define ATTR_COMPACT            0x0000200
-#define ATTR_FACE               0x0000400
-#define ATTR_HEIGHT             0x0000800
-#define ATTR_HREF               0x0001000
-#define ATTR_HSPACE             0x0002000
-#define ATTR_ID                 0x0004000
-#define ATTR_NAME               0x0008000
-#define ATTR_ROWSPAN            0x0010000
-#define ATTR_SIZE               0x0020000
-#define ATTR_SRC                0x0040000
-#define ATTR_START              0x0080000
-#define ATTR_TYPE               0x0100000
-#define ATTR_VALIGN             0x0200000
-#define ATTR_VALUE              0x0400000
-#define ATTR_VSPACE             0x0800000
-#define ATTR_WIDTH              0x1000000
+#define ATTR_ALIGN              1
+#define ATTR_ALT                2
+#define ATTR_BGCOLOR            3
+#define ATTR_BORDER             4
+#define ATTR_CELLPADDING        5
+#define ATTR_CELLSPACING        6
+#define ATTR_CLEAR              7
+#define ATTR_COLOR              8
+#define ATTR_COLSPAN            9
+#define ATTR_COMPACT            10
+#define ATTR_FACE               11
+#define ATTR_HEIGHT             12
+#define ATTR_HREF               13
+#define ATTR_HSPACE             14
+#define ATTR_ID                 15
+#define ATTR_NAME               16
+#define ATTR_ROWSPAN            17
+#define ATTR_SIZE               18
+#define ATTR_SRC                19
+#define ATTR_START              20
+#define ATTR_TYPE               21
+#define ATTR_VALIGN             22
+#define ATTR_VALUE              23
+#define ATTR_VSPACE             24
+#define ATTR_WIDTH              25
+#define AMSK_ALIGN              0x0000001
+#define AMSK_ALT                0x0000002
+#define AMSK_BGCOLOR            0x0000004
+#define AMSK_BORDER             0x0000008
+#define AMSK_CELLPADDING        0x0000010
+#define AMSK_CELLSPACING        0x0000020
+#define AMSK_CLEAR              0x0000040
+#define AMSK_COLOR              0x0000080
+#define AMSK_COLSPAN            0x0000100
+#define AMSK_COMPACT            0x0000200
+#define AMSK_FACE               0x0000400
+#define AMSK_HEIGHT             0x0000800
+#define AMSK_HREF               0x0001000
+#define AMSK_HSPACE             0x0002000
+#define AMSK_ID                 0x0004000
+#define AMSK_NAME               0x0008000
+#define AMSK_ROWSPAN            0x0010000
+#define AMSK_SIZE               0x0020000
+#define AMSK_SRC                0x0040000
+#define AMSK_START              0x0080000
+#define AMSK_TYPE               0x0100000
+#define AMSK_VALIGN             0x0200000
+#define AMSK_VALUE              0x0400000
+#define AMSK_VSPACE             0x0800000
+#define AMSK_WIDTH              0x1000000
 
 static const struct AllowedAttribute {
   const char *zName;
   unsigned int iMask;
 } aAttribute[] = {
   { 0, 0 },
-  { "align",         ATTR_ALIGN,          },
-  { "alt",           ATTR_ALT,            },
-  { "bgcolor",       ATTR_BGCOLOR,        },
-  { "border",        ATTR_BORDER,         },
-  { "cellpadding",   ATTR_CELLPADDING,    },
-  { "cellspacing",   ATTR_CELLSPACING,    },
-  { "clear",         ATTR_CLEAR,          },
-  { "color",         ATTR_COLOR,          },
-  { "colspan",       ATTR_COLSPAN,        },
-  { "compact",       ATTR_COMPACT,        },
-  { "face",          ATTR_FACE,           },
-  { "height",        ATTR_HEIGHT,         },
-  { "href",          ATTR_HREF,           },
-  { "hspace",        ATTR_HSPACE,         },
-  { "id",            ATTR_ID,             },
-  { "name",          ATTR_NAME,           },
-  { "rowspan",       ATTR_ROWSPAN,        },
-  { "size",          ATTR_SIZE,           },
-  { "src",           ATTR_SRC,            },
-  { "start",         ATTR_START,          },
-  { "type",          ATTR_TYPE,           },
-  { "valign",        ATTR_VALIGN,         },
-  { "value",         ATTR_VALUE,          },
-  { "vspace",        ATTR_VSPACE,         },
-  { "width",         ATTR_WIDTH,          },
+  { "align",         AMSK_ALIGN,          },
+  { "alt",           AMSK_ALT,            },
+  { "bgcolor",       AMSK_BGCOLOR,        },
+  { "border",        AMSK_BORDER,         },
+  { "cellpadding",   AMSK_CELLPADDING,    },
+  { "cellspacing",   AMSK_CELLSPACING,    },
+  { "clear",         AMSK_CLEAR,          },
+  { "color",         AMSK_COLOR,          },
+  { "colspan",       AMSK_COLSPAN,        },
+  { "compact",       AMSK_COMPACT,        },
+  { "face",          AMSK_FACE,           },
+  { "height",        AMSK_HEIGHT,         },
+  { "href",          AMSK_HREF,           },
+  { "hspace",        AMSK_HSPACE,         },
+  { "id",            AMSK_ID,             },
+  { "name",          AMSK_NAME,           },
+  { "rowspan",       AMSK_ROWSPAN,        },
+  { "size",          AMSK_SIZE,           },
+  { "src",           AMSK_SRC,            },
+  { "start",         AMSK_START,          },
+  { "type",          AMSK_TYPE,           },
+  { "valign",        AMSK_VALIGN,         },
+  { "value",         AMSK_VALUE,          },
+  { "vspace",        AMSK_VSPACE,         },
+  { "width",         AMSK_WIDTH,          },
 };
 
 /*
 ** Use binary search to locate a tag in the aAttribute[] table.
 */
@@ -140,46 +165,47 @@
 #define MARKUP_CENTER            7
 #define MARKUP_CITE              8
 #define MARKUP_CODE              9
 #define MARKUP_DD               10
 #define MARKUP_DFN              11
-#define MARKUP_DL               12
-#define MARKUP_DT               13
-#define MARKUP_EM               14
-#define MARKUP_FONT             15
-#define MARKUP_H1               16
-#define MARKUP_H2               17
-#define MARKUP_H3               18
-#define MARKUP_H4               19
-#define MARKUP_H5               20
-#define MARKUP_H6               21
-#define MARKUP_HR               22
-#define MARKUP_I                23
-#define MARKUP_IMG              24
-#define MARKUP_KBD              25
-#define MARKUP_LI               26
-#define MARKUP_NOBR             27
-#define MARKUP_NOWIKI           28
-#define MARKUP_OL               29
-#define MARKUP_P                30
-#define MARKUP_PRE              31
-#define MARKUP_S                32
-#define MARKUP_SAMP             33
-#define MARKUP_SMALL            34
-#define MARKUP_STRIKE           35
-#define MARKUP_STRONG           36
-#define MARKUP_SUB              37
-#define MARKUP_SUP              38
-#define MARKUP_TABLE            39
-#define MARKUP_TD               40
-#define MARKUP_TH               41
-#define MARKUP_TR               42
-#define MARKUP_TT               43
-#define MARKUP_U                44
-#define MARKUP_UL               45
-#define MARKUP_VAR              46
-#define MARKUP_VERBATIM         47
+#define MARKUP_DIV              12
+#define MARKUP_DL               13
+#define MARKUP_DT               14
+#define MARKUP_EM               15
+#define MARKUP_FONT             16
+#define MARKUP_H1               17
+#define MARKUP_H2               18
+#define MARKUP_H3               19
+#define MARKUP_H4               20
+#define MARKUP_H5               21
+#define MARKUP_H6               22
+#define MARKUP_HR               23
+#define MARKUP_I                24
+#define MARKUP_IMG              25
+#define MARKUP_KBD              26
+#define MARKUP_LI               27
+#define MARKUP_NOBR             28
+#define MARKUP_NOWIKI           29
+#define MARKUP_OL               30
+#define MARKUP_P                31
+#define MARKUP_PRE              32
+#define MARKUP_S                33
+#define MARKUP_SAMP             34
+#define MARKUP_SMALL            35
+#define MARKUP_STRIKE           36
+#define MARKUP_STRONG           37
+#define MARKUP_SUB              38
+#define MARKUP_SUP              39
+#define MARKUP_TABLE            40
+#define MARKUP_TD               41
+#define MARKUP_TH               42
+#define MARKUP_TR               43
+#define MARKUP_TT               44
+#define MARKUP_U                45
+#define MARKUP_UL               46
+#define MARKUP_VAR              47
+#define MARKUP_VERBATIM         48
 
 /*
 ** The various markup is divided into the following types:
 */
 #define MUTYPE_SINGLE      0x0001   /* <img>, <br>, or <hr> */
@@ -209,71 +235,72 @@
   short int iType;         /* The MUTYPE_* code */
   int allowedAttr;         /* Allowed attributes on this markup */
 } aMarkup[] = {
  { 0,               MARKUP_INVALID,      0,                    0  },
  { "a",             MARKUP_A,            MUTYPE_HYPERLINK,
-                    ATTR_HREF|ATTR_NAME },
+                    AMSK_HREF|AMSK_NAME },
  { "address",       MARKUP_ADDRESS,      MUTYPE_BLOCK,         0  },
  { "b",             MARKUP_B,            MUTYPE_FONT,          0  },
  { "big",           MARKUP_BIG,          MUTYPE_FONT,          0  },
  { "blockquote",    MARKUP_BLOCKQUOTE,   MUTYPE_BLOCK,         0  },
- { "br",            MARKUP_BR,           MUTYPE_SINGLE,        ATTR_CLEAR  },
+ { "br",            MARKUP_BR,           MUTYPE_SINGLE,        AMSK_CLEAR  },
  { "center",        MARKUP_CENTER,       MUTYPE_BLOCK,         0  },
  { "cite",          MARKUP_CITE,         MUTYPE_FONT,          0  },
  { "code",          MARKUP_CODE,         MUTYPE_FONT,          0  },
  { "dd",            MARKUP_DD,           MUTYPE_LI,            0  },
  { "dfn",           MARKUP_DFN,          MUTYPE_FONT,          0  },
- { "dl",            MARKUP_DL,           MUTYPE_LIST,          ATTR_COMPACT },
+ { "div",           MARKUP_DIV,          MUTYPE_BLOCK,         AMSK_ID      },
+ { "dl",            MARKUP_DL,           MUTYPE_LIST,          AMSK_COMPACT },
  { "dt",            MARKUP_DT,           MUTYPE_LI,            0  },
  { "em",            MARKUP_EM,           MUTYPE_FONT,          0  },
  { "font",          MARKUP_FONT,         MUTYPE_FONT,
-                    ATTR_COLOR|ATTR_FACE|ATTR_SIZE   },
- { "h1",            MARKUP_H1,           MUTYPE_BLOCK,         ATTR_ALIGN  },
- { "h2",            MARKUP_H2,           MUTYPE_BLOCK,         ATTR_ALIGN  },
- { "h3",            MARKUP_H3,           MUTYPE_BLOCK,         ATTR_ALIGN  },
- { "h4",            MARKUP_H4,           MUTYPE_BLOCK,         ATTR_ALIGN  },
- { "h5",            MARKUP_H5,           MUTYPE_BLOCK,         ATTR_ALIGN  },
- { "h6",            MARKUP_H6,           MUTYPE_BLOCK,         ATTR_ALIGN  },
+                    AMSK_COLOR|AMSK_FACE|AMSK_SIZE   },
+ { "h1",            MARKUP_H1,           MUTYPE_BLOCK,         AMSK_ALIGN  },
+ { "h2",            MARKUP_H2,           MUTYPE_BLOCK,         AMSK_ALIGN  },
+ { "h3",            MARKUP_H3,           MUTYPE_BLOCK,         AMSK_ALIGN  },
+ { "h4",            MARKUP_H4,           MUTYPE_BLOCK,         AMSK_ALIGN  },
+ { "h5",            MARKUP_H5,           MUTYPE_BLOCK,         AMSK_ALIGN  },
+ { "h6",            MARKUP_H6,           MUTYPE_BLOCK,         AMSK_ALIGN  },
  { "hr",            MARKUP_HR,           MUTYPE_SINGLE,
-                    ATTR_ALIGN|ATTR_COLOR|ATTR_SIZE|ATTR_WIDTH  },
+                    AMSK_ALIGN|AMSK_COLOR|AMSK_SIZE|AMSK_WIDTH  },
  { "i",             MARKUP_I,            MUTYPE_FONT,          0  },
  { "img",           MARKUP_IMG,          MUTYPE_SINGLE,
-                    ATTR_ALIGN|ATTR_ALT|ATTR_BORDER|ATTR_HEIGHT|
-                    ATTR_HSPACE|ATTR_SRC|ATTR_VSPACE|ATTR_WIDTH  },
+                    AMSK_ALIGN|AMSK_ALT|AMSK_BORDER|AMSK_HEIGHT|
+                    AMSK_HSPACE|AMSK_SRC|AMSK_VSPACE|AMSK_WIDTH  },
  { "kbd",           MARKUP_KBD,          MUTYPE_FONT,          0  },
  { "li",            MARKUP_LI,           MUTYPE_LI,
-                    ATTR_TYPE|ATTR_VALUE  },
+                    AMSK_TYPE|AMSK_VALUE  },
  { "nobr",          MARKUP_NOBR,         MUTYPE_FONT,          0  },
  { "nowiki",        MARKUP_NOWIKI,       MUTYPE_SPECIAL,       0  },
  { "ol",            MARKUP_OL,           MUTYPE_LIST,
-                    ATTR_START|ATTR_TYPE|ATTR_COMPACT  },
- { "p",             MARKUP_P,            MUTYPE_BLOCK,         ATTR_ALIGN  },
+                    AMSK_START|AMSK_TYPE|AMSK_COMPACT  },
+ { "p",             MARKUP_P,            MUTYPE_BLOCK,         AMSK_ALIGN  },
  { "pre",           MARKUP_PRE,          MUTYPE_BLOCK,         0  },
  { "s",             MARKUP_S,            MUTYPE_FONT,          0  },
  { "samp",          MARKUP_SAMP,         MUTYPE_FONT,          0  },
  { "small",         MARKUP_SMALL,        MUTYPE_FONT,          0  },
  { "strike",        MARKUP_STRIKE,       MUTYPE_FONT,          0  },
  { "strong",        MARKUP_STRONG,       MUTYPE_FONT,          0  },
  { "sub",           MARKUP_SUB,          MUTYPE_FONT,          0  },
  { "sup",           MARKUP_SUP,          MUTYPE_FONT,          0  },
  { "table",         MARKUP_TABLE,        MUTYPE_TABLE,
-                    ATTR_ALIGN|ATTR_BGCOLOR|ATTR_BORDER|ATTR_CELLPADDING|
-                    ATTR_CELLSPACING|ATTR_HSPACE|ATTR_VSPACE  },
+                    AMSK_ALIGN|AMSK_BGCOLOR|AMSK_BORDER|AMSK_CELLPADDING|
+                    AMSK_CELLSPACING|AMSK_HSPACE|AMSK_VSPACE  },
  { "td",            MARKUP_TD,           MUTYPE_TD,
-                    ATTR_ALIGN|ATTR_BGCOLOR|ATTR_COLSPAN|
-                    ATTR_ROWSPAN|ATTR_VALIGN  },
+                    AMSK_ALIGN|AMSK_BGCOLOR|AMSK_COLSPAN|
+                    AMSK_ROWSPAN|AMSK_VALIGN  },
  { "th",            MARKUP_TH,           MUTYPE_TD,
-                    ATTR_ALIGN|ATTR_BGCOLOR|ATTR_COLSPAN|
-                    ATTR_ROWSPAN|ATTR_VALIGN  },
+                    AMSK_ALIGN|AMSK_BGCOLOR|AMSK_COLSPAN|
+                    AMSK_ROWSPAN|AMSK_VALIGN  },
  { "tr",            MARKUP_TR,           MUTYPE_TR,
-                    ATTR_ALIGN|ATTR_BGCOLOR||ATTR_VALIGN  },
+                    AMSK_ALIGN|AMSK_BGCOLOR||AMSK_VALIGN  },
  { "tt",            MARKUP_TT,           MUTYPE_FONT,          0  },
  { "u",             MARKUP_U,            MUTYPE_FONT,          0  },
  { "ul",            MARKUP_UL,           MUTYPE_LIST,
-                    ATTR_TYPE|ATTR_COMPACT  },
+                    AMSK_TYPE|AMSK_COMPACT  },
  { "var",           MARKUP_VAR,          MUTYPE_FONT,          0  },
- { "verbatim",      MARKUP_VERBATIM,     MUTYPE_SPECIAL,       ATTR_ID },
+ { "verbatim",      MARKUP_VERBATIM,     MUTYPE_SPECIAL,       AMSK_ID },
 };
 
 /*
 ** Use binary search to locate a tag in the aMarkup[] table.
 */
@@ -332,11 +359,15 @@
   int wantAutoParagraph;      /* True if a <p> is desired */
   int inAutoParagraph;        /* True if within an automatic paragraph */
   const char *zVerbatimId;    /* The id= attribute of <verbatim> */
   int nStack;                 /* Number of elements on the stack */
   int nAlloc;                 /* Space allocated for aStack */
-  unsigned char *aStack;      /* Open markup stack */
+  struct sStack {
+    short iCode;                 /* Markup code */
+    short allowWiki;             /* ALLOW_WIKI if wiki allowed before tag */
+    const char *zId;             /* ID attribute or NULL */
+  } *aStack;
 };
 
 
 /*
 ** z points to a "<" character.  Check to see if this is the start of
@@ -585,11 +616,11 @@
   unsigned char endTag;   /* True if </...> instead of <...> */
   unsigned char iCode;    /* MARKUP_* */
   unsigned char nAttr;    /* Number of attributes */
   unsigned short iType;   /* MUTYPE_* */
   struct {
-    unsigned char iCode;     /* ATTR_* */
+    unsigned char iACode;    /* ATTR_* */
     char *zValue;            /* Argument to this attribute.  Might be NULL */
     char cTerm;              /* Original argument termination character */
   } aAttr[10];
 };
 
@@ -600,11 +631,11 @@
 ** The content of z[] might be modified by converting characters
 ** to lowercase and by inserting some "\000" characters.
 */
 static void parseMarkup(ParsedMarkup *p, char *z){
   int i, j, c;
-  int iCode;
+  int iACode;
   char *zValue;
   int seen = 0;
   char zTag[100];
 
   if( z[1]=='/' ){
@@ -630,12 +661,12 @@
     while( isalnum(z[i]) ){
       if( j<sizeof(zTag)-1 ) zTag[j++] = tolower(z[i]);
       i++;
     }
     zTag[j] = 0;
-    p->aAttr[p->nAttr].iCode = iCode = findAttr(zTag);
-    attrOk = iCode!=0 && (seen & aAttribute[iCode].iMask)==0;
+    p->aAttr[p->nAttr].iACode = iACode = findAttr(zTag);
+    attrOk = iACode!=0 && (seen & aAttribute[iACode].iMask)==0;
     while( isspace(z[i]) ){ z++; }
     if( z[i]!='=' ){
       p->aAttr[p->nAttr].zValue = 0;
       p->aAttr[p->nAttr].cTerm = 0;
       c = 0;
@@ -656,11 +687,11 @@
         z[i] = 0;
       }
       i++;
     }
     if( attrOk ){
-      seen |= aAttribute[iCode].iMask;
+      seen |= aAttribute[iACode].iMask;
       p->nAttr++;
     }
     while( isspace(z[i]) ){ i++; }
     if( z[i]=='>' || (z[i]=='/' && z[i+1]=='>') ) break;
   }
@@ -674,11 +705,11 @@
   if( p->endTag ){
     blob_appendf(pOut, "</%s>", aMarkup[p->iCode].zName);
   }else{
     blob_appendf(pOut, "<%s", aMarkup[p->iCode].zName);
     for(i=0; i<p->nAttr; i++){
-      blob_appendf(pOut, " %s", aAttribute[p->aAttr[i].iCode].zName);
+      blob_appendf(pOut, " %s", aAttribute[p->aAttr[i].iACode].zName);
       if( p->aAttr[i].zValue ){
         blob_appendf(pOut, "=\"%s\"", p->aAttr[i].zValue);
       }
     }
     blob_append(pOut, ">", 1);
@@ -699,47 +730,91 @@
     z[n] = p->aAttr[i].cTerm;
   }
 }
 
 /*
+** Return the ID attribute for markup.  Return NULL if there is no
+** ID attribute.
+*/
+static const char *markupId(ParsedMarkup *p){
+  int i;
+  for(i=0; i<p->nAttr; i++){
+    if( p->aAttr[i].iACode==ATTR_ID ){
+      return p->aAttr[i].zValue;
+    }
+  }
+  return 0;
+}
+
+/*
 ** Pop a single element off of the stack.  As the element is popped,
-** output its end tag.
+** output its end tag if it is not a </div> tag.
 */
 static void popStack(Renderer *p){
   if( p->nStack ){
+    int iCode;
     p->nStack--;
-    blob_appendf(p->pOut, "</%s>", aMarkup[p->aStack[p->nStack]].zName);
+    iCode = p->aStack[p->nStack].iCode;
+    if( iCode!=MARKUP_DIV ){
+      blob_appendf(p->pOut, "</%s>", aMarkup[iCode].zName);
+    }
   }
 }
 
 /*
 ** Push a new markup value onto the stack.  Enlarge the stack
 ** if necessary.
 */
-static void pushStack(Renderer *p, int elem){
+static void pushStackWithId(Renderer *p, int elem, const char *zId, int w){
   if( p->nStack>=p->nAlloc ){
     p->nAlloc = p->nAlloc*2 + 100;
-    p->aStack = realloc(p->aStack, p->nAlloc);
+    p->aStack = realloc(p->aStack, p->nAlloc*sizeof(p->aStack[0]));
     if( p->aStack==0 ){
       fossil_panic("out of memory");
     }
   }
-  p->aStack[p->nStack++] = elem;
+  p->aStack[p->nStack].iCode = elem;
+  p->aStack[p->nStack].zId = zId;
+  p->aStack[p->nStack].allowWiki = w;
+  p->nStack++;
+}
+static void pushStack(Renderer *p, int elem){
+  pushStackWithId(p, elem, 0, 0);
 }
 
 /*
 ** Pop the stack until the top-most iTag element is removed.
 ** If there is no iTag element on the stack, this routine
 ** is a no-op.
 */
 static void popStackToTag(Renderer *p, int iTag){
   int i;
-  for(i=p->nStack-1; i>=0 && p->aStack[i]!=iTag; i--){}
+  for(i=p->nStack-1; i>=0; i--){
+    if( p->aStack[i].iCode!=iTag ) continue;
+    if( p->aStack[i].zId ) continue;
+    break;
+  }
   if( i<0 ) return;
   while( p->nStack>i ){
     popStack(p);
   }
+}
+
+/*
+** Attempt to find a find a tag of type iTag with id zId.  Return -1
+** if not found.  If found, return its stack level.
+*/
+static int findTagWithId(Renderer *p, int iTag, const char *zId){
+  int i;
+  assert( zId!=0 );
+  for(i=p->nStack-1; i>=0; i--){
+    if( p->aStack[i].iCode!=iTag ) continue;
+    if( p->aStack[i].zId==0 ) continue;
+    if( strcmp(zId, p->aStack[i].zId)!=0 ) continue;
+    break;
+  }
+  return i;
 }
 
 /*
 ** Pop the stack until the top-most element of the stack
 ** is an element that matches the type in iMask.  Return
@@ -748,17 +823,19 @@
 ** that matches iMask, then leave the stack unchanged and
 ** return false (MARKUP_INVALID).
 */
 static int backupToType(Renderer *p, int iMask){
   int i;
-  for(i=p->nStack-1; i>=0 && (aMarkup[p->aStack[i]].iType&iMask)==0; i--){}
+  for(i=p->nStack-1; i>=0; i--){
+    if( aMarkup[p->aStack[i].iCode].iType & iMask ) break;
+  }
   if( i<0 ) return 0;
   i++;
   while( p->nStack>i ){
     popStack(p);
   }
-  return p->aStack[i-1];
+  return p->aStack[i-1].iCode;
 }
 
 /*
 ** Begin a new paragraph if that something that is needed.
 */
@@ -849,11 +926,11 @@
 /*
 ** Return the MUTYPE for the top of the stack.
 */
 static int stackTopType(Renderer *p){
   if( p->nStack<=0 ) return 0;
-  return aMarkup[p->aStack[p->nStack-1]].iType;
+  return aMarkup[p->aStack[p->nStack-1].iCode].iType;
 }
 
 /*
 ** Convert the wiki in z[] into html in the renderer p.  The
 ** renderer has already been initialized.
@@ -987,11 +1064,41 @@
         startAutoParagraph(p);
         blob_append(p->pOut, z, n);
         break;
       }
       case TOKEN_MARKUP: {
+        const char *zId;
+        int iDiv;
         parseMarkup(&markup, z);
+
+        /* Markup of the form </div id=ID> where there is a matching
+        ** ID somewhere on the stack.  Exit the verbatim if were are in
+        ** it.  Pop the stack up to the matching <div>.  Discard the
+        ** </div>
+        */
+        if( markup.iCode==MARKUP_DIV && markup.endTag &&
+             (zId = markupId(&markup))!=0 &&
+             (iDiv = findTagWithId(p, MARKUP_DIV, zId))>=0
+        ){
+          if( p->inVerbatim ){
+            p->inVerbatim = 0;
+            p->state = p->preVerbState;
+            blob_append(p->pOut, "</pre>", 6);
+          }
+          while( p->nStack>iDiv+1 ) popStack(p);
+          if( p->aStack[iDiv].allowWiki ){
+            p->state |= ALLOW_WIKI;
+          }else{
+            p->state &= ~ALLOW_WIKI;
+          }
+          assert( p->nStack==iDiv+1 );
+          p->nStack--;
+        }else
+
+        /* If within <verbatim id=ID> ignore everything other than
+        ** </verbatim id=ID> and the </dev id=ID2> above.
+        */
         if( p->inVerbatim ){
           if( endVerbatim(p, &markup) ){
             p->inVerbatim = 0;
             p->state = p->preVerbState;
             blob_append(p->pOut, "</pre>", 6);
@@ -998,29 +1105,60 @@
           }else{
             unparseMarkup(&markup);
             blob_append(p->pOut, "&lt;", 4);
             n = 1;
           }
-        }else if( markup.iCode==MARKUP_INVALID ){
+        }else
+
+        /* Render invalid markup literally.  The markup appears in the
+        ** final output as plain text.
+        */
+        if( markup.iCode==MARKUP_INVALID ){
           unparseMarkup(&markup);
           startAutoParagraph(p);
           blob_append(p->pOut, "&lt;", 4);
           n = 1;
-        }else if( (markup.iType&MUTYPE_FONT)==0
-                    && (p->state & FONT_MARKUP_ONLY)!=0 ){
+        }else
+
+        /* If the markup is not font-change markup ignore it if the
+        ** font-change-only flag is set.
+        */
+        if( (markup.iType&MUTYPE_FONT)==0 && (p->state & FONT_MARKUP_ONLY)!=0 ){
           /* Do nothing */
-        }else if( inlineOnly && (markup.iType&MUTYPE_INLINE)==0 ){
+        }else
+
+        /* Ignore block markup for in-line rendering.
+        */
+        if( inlineOnly && (markup.iType&MUTYPE_INLINE)==0 ){
           /* Do nothing */
-        }else if( markup.iCode==MARKUP_NOWIKI ){
+        }else
+
+        if( markup.iCode==MARKUP_NOWIKI ){
           if( markup.endTag ){
             p->state |= ALLOW_WIKI;
           }else{
             p->state &= ~ALLOW_WIKI;
           }
-        }else if( markup.endTag ){
+        }else
+
+        /* Generate end-tags */
+        if( markup.endTag ){
           popStackToTag(p, markup.iCode);
-        }else if( markup.iCode==MARKUP_VERBATIM ){
+        }else
+
+        /* Push <div> markup onto the stack together with the id=ID attribute.
+        */
+        if( markup.iCode==MARKUP_DIV ){
+          pushStackWithId(p, markup.iCode, markupId(&markup),
+                          (p->state & ALLOW_WIKI)!=0);
+        }else
+
+        /* Enter <verbatim> processing.  With verbatim enabled, all other
+        ** markup other than the corresponding end-tag with the same ID is
+        ** ignored.
+        */
+        if( markup.iCode==MARKUP_VERBATIM ){
           if( markup.nAttr==1 ){
             p->zVerbatimId = markup.aAttr[0].zValue;
           }else{
             p->zVerbatimId = 0;
           }
@@ -1027,37 +1165,42 @@
           p->inVerbatim = 1;
           p->preVerbState = p->state;
           p->state &= ~ALLOW_WIKI;
           blob_append(p->pOut, "<pre class='verbatim'>",-1);
           p->wantAutoParagraph = 0;
-        }else if( markup.iType==MUTYPE_LI ){
+        }else
+        if( markup.iType==MUTYPE_LI ){
           if( backupToType(p, MUTYPE_LIST)==0 ){
             pushStack(p, MARKUP_UL);
             blob_append(p->pOut, "<ul>", 4);
           }
           pushStack(p, MARKUP_LI);
           renderMarkup(p->pOut, &markup);
-        }else if( markup.iType==MUTYPE_TR ){
+        }else
+        if( markup.iType==MUTYPE_TR ){
           if( backupToType(p, MUTYPE_TABLE) ){
             pushStack(p, MARKUP_TR);
             renderMarkup(p->pOut, &markup);
           }
-        }else if( markup.iType==MUTYPE_TD ){
+        }else
+        if( markup.iType==MUTYPE_TD ){
           if( backupToType(p, MUTYPE_TABLE|MUTYPE_TR) ){
             if( stackTopType(p)==MUTYPE_TABLE ){
               pushStack(p, MARKUP_TR);
               blob_append(p->pOut, "<tr>", 4);
             }
             pushStack(p, markup.iCode);
             renderMarkup(p->pOut, &markup);
           }
-        }else if( markup.iType==MUTYPE_HYPERLINK ){
+        }else
+        if( markup.iType==MUTYPE_HYPERLINK ){
           popStackToTag(p, markup.iCode);
           startAutoParagraph(p);
           renderMarkup(p->pOut, &markup);
           pushStack(p, markup.iCode);
-        }else{
+        }else
+        {
           if( markup.iType==MUTYPE_FONT ){
             startAutoParagraph(p);
           }else if( markup.iType==MUTYPE_BLOCK ){
             p->wantAutoParagraph = 0;
           }