From 4f49b16079b27d7ce633619e4cddfdf2bffbc952 Mon Sep 17 00:00:00 2001 From: cashmere Date: Fri, 4 Sep 2026 09:05:24 +0200 Subject: initial commit --- src/markdown_html.c | 1013 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1013 insertions(+) create mode 100644 src/markdown_html.c (limited to 'src/markdown_html.c') diff --git a/src/markdown_html.c b/src/markdown_html.c new file mode 100644 index 0000000..d71f5a0 --- /dev/null +++ b/src/markdown_html.c @@ -0,0 +1,1013 @@ +/* +** Copyright (c) 2012 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) + +** 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. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file contains callbacks for the markdown parser that generate +** XHTML output. +*/ + +#include "config.h" +#include "markdown_html.h" + +#if INTERFACE + +void markdown_to_html( + struct Blob *input_markdown, + struct Blob *output_title, + struct Blob *output_body); + +#endif /* INTERFACE */ + +/* +** Markdown-internal helper for generating unique link reference IDs. +** Fields provide typed interpretation of the underline memory buffer. +*/ +typedef union bitfield64_t bitfield64_t; +union bitfield64_t{ + char c[8]; /* interpret as the array of signed characters */ + unsigned char b[8]; /* interpret as the array of unsigned characters */ +}; + +/* +** An instance of the following structure is passed through the +** "opaque" pointer. +*/ +typedef struct MarkdownToHtml MarkdownToHtml; +struct MarkdownToHtml { + Blob *output_title; /* Store the title here */ + bitfield64_t unique; /* Enables construction of unique #id elements */ + + #ifndef FOOTNOTES_WITHOUT_URI + Blob reqURI; /* REQUEST_URI with escaped quotes */ + #endif +}; + + +/* INTER_BLOCK -- skip a line between block level elements */ +#define INTER_BLOCK(ob) \ + do { if( blob_size(ob)>0 ) blob_append_char(ob, '\n'); } while (0) + +/* +** FOOTNOTES_WITHOUT_URI macro was introduced by [2c1f8f3592ef00e0] +** to enable flexibility in rendering of footnote-specific hyperlinks. +** It may be defined for a particular build in order to omit +** full REQUEST_URIs within footnote-specific (and page-local) hyperlinks. +** This *is* used for the builds that incorporate 'base-href-fix' branch +** (which in turn fixes footnotes on the preview tab of /wikiedit page). +*/ +#ifndef FOOTNOTES_WITHOUT_URI + #define BLOB_APPEND_URI(dest,ctx) blob_appendb(dest,&((ctx)->reqURI)) +#else + #define BLOB_APPEND_URI(dest,ctx) +#endif + +/* Converts an integer to a textual base26 representation +** with proper null-termination. + * Return empty string if that integer is negative. */ +static bitfield64_t to_base26(int i, int uppercase){ + bitfield64_t x; + int j; + memset( &x, 0, sizeof(x) ); + if( i >= 0 ){ + for(j=7; j >= 0; j--){ + x.b[j] = (unsigned char)(uppercase?'A':'a') + i%26; + if( (i /= 26) == 0 ) break; + } + assert( j > 0 ); /* because 2^32 < 26^7 */ + for(i=0; i<8-j; i++) x.b[i] = x.b[i+j]; + for( ; i<8 ; i++) x.b[i] = 0; + } + assert( x.c[7] == 0 ); + return x; +} + +/* HTML escapes +** +** html_escape() converts < to <, > to >, and & to &. +** html_quote() goes further and converts " into " and ' in '. +*/ +static void html_quote(struct Blob *ob, const char *data, size_t size){ + size_t beg = 0, i = 0; + while( i' ){ + blob_append_literal(ob, ">"); + }else if( data[i]=='&' ){ + blob_append_literal(ob, "&"); + }else if( data[i]=='"' ){ + blob_append_literal(ob, """); + }else if( data[i]=='\'' ){ + blob_append_literal(ob, "'"); + }else{ + break; + } + i++; + } + } +} +static void html_escape(struct Blob *ob, const char *data, size_t size){ + size_t beg = 0, i = 0; + while( i' ){ + blob_append_literal(ob, ">"); + }else if( data[i]=='&' ){ + blob_append_literal(ob, "&"); + }else{ + break; + } + i++; + } + } +} + + +/* HTML block tags */ + +/* Size of the prolog: "
\n" */ +#define PROLOG_SIZE 23 + +static void html_prolog(struct Blob *ob, void *opaque){ + INTER_BLOCK(ob); + blob_append_literal(ob, "
\n"); + assert( blob_size(ob)==PROLOG_SIZE ); +} + +static void html_epilog(struct Blob *ob, void *opaque){ + INTER_BLOCK(ob); + blob_append_literal(ob, "
\n"); +} + +static void html_blockhtml(struct Blob *ob, struct Blob *text, void *opaque){ + char *data = blob_buffer(text); + size_t size = blob_size(text); + Blob *title = ((MarkdownToHtml*)opaque)->output_title; + while( size>0 && fossil_isspace(data[0]) ){ data++; size--; } + while( size>0 && fossil_isspace(data[size-1]) ){ size--; } + /* If the first raw block is an

element, then use it as the title. */ + if( blob_size(ob)<=PROLOG_SIZE + && size>9 + && title!=0 + && sqlite3_strnicmp("", &data[size-5],5)==0 + ){ + int nTag = html_tag_length(data); + blob_append(title, data+nTag, size - nTag - 5); + return; + } + INTER_BLOCK(ob); + blob_append(ob, data, size); + blob_append_literal(ob, "\n"); +} + +static void html_blockcode(struct Blob *ob, struct Blob *text, void *opaque){ + INTER_BLOCK(ob); + blob_append_literal(ob, "
");
+  html_escape(ob, blob_buffer(text), blob_size(text));
+  blob_append_literal(ob, "
\n"); +} + +static void html_blockquote(struct Blob *ob, struct Blob *text, void *opaque){ + INTER_BLOCK(ob); + blob_append_literal(ob, "
\n"); + blob_appendb(ob, text); + blob_append_literal(ob, "
\n"); +} + +static void html_header( + struct Blob *ob, + struct Blob *text, + int level, + void *opaque +){ + struct Blob *title = ((MarkdownToHtml*)opaque)->output_title; + char *z = 0; + int i,j; + /* The first header at the beginning of a text is considered as + * a title and not output. */ + if( blob_size(ob)<=PROLOG_SIZE && title!=0 && blob_size(title)==0 ){ + blob_appendb(title, text); + return; + } + INTER_BLOCK(ob); + z = fossil_strdup(blob_buffer(text)); + if( z==0 ){ + j = 0; + }else{ + /* + ** The GitHub "slugify" algorithm converts the text of a markdown header + ** into a ID for that header. The algorithm is: + ** + ** 1. ASCII alphanumerics -> convert to lower case + ** 2. Spaces, hyphens, underscores -> convert to '-' + ** 3. Non-ASCII -> preserve as-is + ** 4. Other punctuation -> remove + ** 5. Multiple consecutive dashes -> collapse to one + ** 6. Leading and trailing dashes -> remove + ** 7. Markup <...> and &...; -> remove + ** + ** This implementation does the conversion in-place. + */ + for(i=j=0; z[i]; i++){ + if( fossil_isalnum(z[i]) ){ + z[j++] = fossil_tolower(z[i]); + }else if( fossil_isspace(z[i]) || z[i]=='-' || z[i]=='_' ){ + if( j>0 && z[j-1]!='-' ) z[j++] = '-'; + }else if( z[i]=='<' ){ + do{ i++; }while( z[i]!=0 && z[i]!='>' ); + }else if( z[i]=='&' ){ + do{ i++; }while( z[i]!=0 && z[i]!=';' ); + }else if( (z[i]&0x80)!=0 ){ + z[j++] = z[i]; + } + } + if( j>0 && z[j-1]=='-' ) j--; + z[j] = 0; + } + if( j>0 ){ + blob_appendf(ob, "", level, z); + }else{ + blob_appendf(ob, "", level); + } + blob_appendb(ob, text); + blob_appendf(ob, "", level); + fossil_free(z); +} + +static void html_hrule(struct Blob *ob, void *opaque){ + INTER_BLOCK(ob); + blob_append_literal(ob, "
\n"); +} + + +static void html_list( + struct Blob *ob, + struct Blob *text, + int flags, + void *opaque +){ + char ol[] = "ol"; + char ul[] = "ul"; + char *tag = (flags & MKD_LIST_ORDERED) ? ol : ul; + INTER_BLOCK(ob); + blob_appendf(ob, "<%s>\n", tag); + blob_appendb(ob, text); + blob_appendf(ob, "\n", tag); +} + +static void html_list_item( + struct Blob *ob, + struct Blob *text, + int flags, + void *opaque +){ + char *text_data = blob_buffer(text); + size_t text_size = blob_size(text); + while( text_size>0 && text_data[text_size-1]=='\n' ) text_size--; + blob_append_literal(ob, "
  • "); + blob_append(ob, text_data, text_size); + blob_append_literal(ob, "
  • \n"); +} + +static void html_paragraph(struct Blob *ob, struct Blob *text, void *opaque){ + INTER_BLOCK(ob); + blob_append_literal(ob, "

    "); + blob_appendb(ob, text); + blob_append_literal(ob, "

    \n"); +} + + +static void html_table( + struct Blob *ob, + struct Blob *head_row, + struct Blob *rows, + void *opaque +){ + INTER_BLOCK(ob); + blob_append_literal(ob, "\n"); + if( head_row && blob_size(head_row)>0 ){ + blob_append_literal(ob, "\n"); + blob_appendb(ob, head_row); + blob_append_literal(ob, "\n\n"); + } + if( rows ){ + blob_appendb(ob, rows); + } + if( head_row && blob_size(head_row)>0 ){ + blob_append_literal(ob, "\n"); + } + blob_append_literal(ob, "
    \n"); +} + +static void html_table_cell( + struct Blob *ob, + struct Blob *text, + int flags, + void *opaque +){ + if( flags & MKD_CELL_HEAD ){ + blob_append_literal(ob, " "); + blob_appendb(ob, text); + if( flags & MKD_CELL_HEAD ){ + blob_append_literal(ob, "\n"); + }else{ + blob_append_literal(ob, "\n"); + } +} + +static void html_table_row( + struct Blob *ob, + struct Blob *cells, + int flags, + void *opaque +){ + blob_append_literal(ob, " \n"); + blob_appendb(ob, cells); + blob_append_literal(ob, " \n"); +} + +/* +** Render a token of user provided classes. +** If bHTML is true then render HTML for (presumably) visible text, +** otherwise just a space-separated list of the derived classes. +*/ +static void append_footnote_upc( + struct Blob *ob, + const struct Blob *upc, /* token of user-provided classes */ + int bHTML +){ + const char *z = blob_buffer(upc); + int i, n = blob_size(upc); + + if( n<3 ) return; + assert( z[0]=='.' && z[n-1] == ':' ); + if( bHTML ){ + blob_append_literal(ob, "" + "."); + } + n = 0; + do{ + z++; + if( *z!='.' && *z!=':' ){ + assert( fossil_isalnum(*z) || *z=='-' ); + n++; + continue; + } + assert( n ); + if( bHTML ) blob_append_literal(ob, ""); + blob_append(ob, z-n, n); + blob_append_literal(ob, ""); + }else{ + blob_append_char(ob, ' '); + } + n = 0; + if( bHTML ){ + if( *z==':' ){ + blob_append_literal(ob,":"); + }else{ + blob_append_literal(ob,"."); + } + } + }while( *z != ':' ); + if( bHTML ) blob_append_literal(ob,"\n"); +} + +static int html_footnote_ref( + struct Blob *ob, const struct Blob *span, const struct Blob *upc, + int iMark, int locus, void *opaque +){ + const struct MarkdownToHtml* ctx = (struct MarkdownToHtml*)opaque; + const bitfield64_t l = to_base26(locus-1,0); + char pos[32]; + memset(pos,0,32); + assert( locus > 0 ); + /* expect BUGs if the following yields compiler warnings */ + if( iMark > 0 ){ /* a regular reference to a footnote */ + sqlite3_snprintf(sizeof(pos), pos, "%s-%d-%s", ctx->unique.c, iMark, l.c); + if(span && blob_size(span)) { + blob_append_literal(ob,"",pos); + blob_appendb(ob, span); + blob_trim(ob); + blob_append_literal(ob,"%d", pos, iMark); + }else{ + blob_trim(ob); + blob_append_literal(ob,"%d", + pos, pos, iMark); + } + }else{ /* misreference */ + assert( iMark == -1 ); + + sqlite3_snprintf(sizeof(pos), pos, "%s-%s", ctx->unique.c, l.c); + if(span && blob_size(span)) { + blob_appendf(ob, "", pos); + blob_appendb(ob, span); + blob_trim(ob); + blob_append_literal(ob, "misref", pos); + }else{ + blob_trim(ob); + blob_append_literal(ob, "", pos, pos); + blob_append_literal(ob, "misref"); + } + } + return 1; +} + +/* Render a single item of the footnotes list. + * Each backref gets a unique id to enable dynamic styling. */ +static void html_footnote_item( + struct Blob *ob, const struct Blob *text, int iMark, int nUsed, void *opaque +){ + const struct MarkdownToHtml* ctx = (struct MarkdownToHtml*)opaque; + const char * const unique = ctx->unique.c; + assert( nUsed >= 0 ); + /* expect BUGs if the following yields compiler warnings */ + + if( iMark < 0 ){ /* misreferences */ + assert( iMark == -1 ); + assert( nUsed ); + blob_append_literal(ob,"
  • " + ""); + if( nUsed == 1 ){ + blob_appendf(ob,"^", unique); + }else{ + int i; + blob_append_char(ob, '^'); + for(i=0; i%c", unique,c, c); + } + if( i < nUsed ) blob_append_literal(ob," …"); + } + blob_append_literal(ob,"\nMisreference"); + }else if( iMark > 0 ){ /* regular, joined and overnested footnotes */ + char pos[24]; + int bJoin = 0; + #define _joined_footnote_indicator "
      " + #define _jfi_sz (sizeof(_joined_footnote_indicator)-1) + /* make.footnote_item() invocations should pass args accordingly */ + const struct Blob *upc = text+1; + assert( text ); + /* allow blob_size(text)==0 for constructs like [...](^ [] ()) */ + memset(pos,0,24); + sqlite3_snprintf(sizeof(pos), pos, "%s-%d", unique, iMark); + blob_appendf(ob, "
    • "); + blob_appendf(ob,"^", pos); + }else{ + int i; + blob_append_literal(ob, "fn-polyref'>^"); + for(i=0; i%c", pos,c, c); + } + /* It's unlikely that so many backrefs will be usefull */ + /* but maybe for some machine generated documents... */ + for(; i%s", pos,l.c, l.c); + } + if( i < nUsed ) blob_append_literal(ob," …"); + } + blob_append_literal(ob,"\n"); + if( bJoin ){ + blob_append_literal(ob,"
        "); + blob_append(ob,blob_buffer(text)+_jfi_sz,blob_size(text)-_jfi_sz); + }else if( nUsed ){ + append_footnote_upc(ob, upc, 1); + blob_appendb(ob, text); + }else{ + blob_append_literal(ob,"\n" + "
        ");
        +      if( blob_size(upc) ){
        +        blob_appendb(ob, upc);
        +      }
        +      html_escape(ob, blob_buffer(text), blob_size(text));
        +      blob_append_literal(ob,"
        "); + } + #undef _joined_footnote_indicator + #undef _jfi_sz + }else{ /* a footnote was defined but wasn't referenced */ + /* make.footnote_item() invocations should pass args accordingly */ + const struct Blob *id = text-1, *upc = text+1; + assert( !nUsed ); + assert( text ); + assert( blob_size(text) ); + assert( blob_size(id) ); + blob_append_literal(ob,"
      • \n[^ "); + html_escape(ob, blob_buffer(id), blob_size(id)); + blob_append_literal(ob, " ]\n" + "
        ");
        +    if( blob_size(upc) ){
        +      blob_appendb(ob, upc);
        +    }
        +    html_escape(ob, blob_buffer(text), blob_size(text));
        +    blob_append_literal(ob,"
        "); + } + blob_append_literal(ob, "\n
      • \n"); +} + +static void html_footnotes( + struct Blob *ob, const struct Blob *items, void *opaque +){ + if( items && blob_size(items) ){ + blob_append_literal(ob, + "\n
        \n
          \n"); + blob_appendb(ob, items); + blob_append_literal(ob, "
        \n"); + } +} + +/* HTML span tags */ + +static int html_raw_html_tag(struct Blob *ob, struct Blob *text, void *opaque){ + blob_append(ob, blob_buffer(text), blob_size(text)); + return 1; +} + +static int html_autolink( + struct Blob *ob, + struct Blob *link, + enum mkd_autolink type, + void *opaque +){ + if( !link || blob_size(link)<=0 ) return 0; + blob_append_literal(ob, ""); + if( type==MKDA_EXPLICIT_EMAIL && blob_size(link)>7 ){ + /* remove "mailto:" from displayed text */ + html_escape(ob, blob_buffer(link)+7, blob_size(link)-7); + }else{ + html_escape(ob, blob_buffer(link), blob_size(link)); + } + blob_append_literal(ob, ""); + return 1; +} + +/* +** Flags for use with/via pikchr_to_html_add_flags(). +*/ +static int pikchrToHtmlFlags = 0; +/* +** Sets additional pikchr_process() flags to use for all future calls +** to pikch_to_html(). This is intended to be used by commands such as +** test-wiki-render and test-markdown-render to set the +** PIKCHR_PROCESS_DARK_MODE flag for all embedded pikchr elements. +** +** Not all PIKCHR_PROCESS flags are legal, as pikchr_to_html() +** hard-codes a subset of flags and passing arbitrary flags here may +** interfere with that. +** +** The only tested/intended use of this function is to pass it either +** 0 or PIKCHR_PROCESS_DARK_MODE. +** +** Design note: this is not implemented as an additional argument to +** pikchr_to_html() because the commands for which dark-mode rendering +** are now supported (test-wiki-render and test-markdown-render) are +** far removed from their corresponding pikchr_to_html() calls and +** there is no direct path from those commands to those calls. A +** cleaner, but much more invasive, approach would be to add a flag to +** markdown_to_html(), extend the WIKI_... flags with +** WIKI_DARK_PIKCHR, and extend both wiki.c:Renderer and +** markdown_html.c:MarkdownToHtml to contain and pass on that flag. +*/ +void pikchr_to_html_add_flags( int f ){ + pikchrToHtmlFlags = f; +} + +/* +** The nSrc bytes at zSrc[] are Pikchr input text (allegedly). Process that +** text and insert the result in place of the original. +*/ +void pikchr_to_html( + Blob *ob, /* Write the generated SVG here */ + const char *zSrc, int nSrc, /* The Pikchr source text */ + const char *zArg, int nArg /* Addition arguments */ +){ + int pikFlags = PIKCHR_PROCESS_NONCE + | PIKCHR_PROCESS_DIV + | PIKCHR_PROCESS_SRC + | PIKCHR_PROCESS_ERR_PRE + | pikchrToHtmlFlags; + Blob bSrc = empty_blob; + const char *zPikVar; + double rPikVar; + + while( nArg>0 ){ + int i; + for(i=0; i=0.1 + && rPikVar<10.0 + ){ + blob_appendf(&bSrc, "scale = %.13g\n", rPikVar); + } + zPikVar = skin_detail("pikchr-fontscale"); + if( zPikVar + && (rPikVar = atof(zPikVar))>=0.1 + && rPikVar<10.0 + ){ + blob_appendf(&bSrc, "fontscale = %.13g\n", rPikVar); + } + blob_append(&bSrc, zSrc, nSrc) + /*have to dup input to ensure a NUL-terminated source string */; + pikchr_process(blob_str(&bSrc), pikFlags, ob); + blob_reset(&bSrc); +} + +/* Invoked for `...` blocks where there are nSep grave accents in a +** row that serve as the delimiter. According to CommonMark: +** +** * https://spec.commonmark.org/0.29/#fenced-code-blocks +** * https://spec.commonmark.org/0.29/#code-spans +** +** If nSep is 1 or 2, then this is a code-span which is inline. +** If nSep is 3 or more, then this is a fenced code block +*/ +static int html_codespan( + struct Blob *ob, /* Write the output here */ + struct Blob *text, /* The stuff in between the code span marks */ + int nSep, /* Number of grave accents marks as delimiters */ + void *opaque +){ + if( text==0 ){ + /* no-op */ + }else if( nSep<=2 ){ + /* One or two graves: an in-line code span */ + blob_append_literal(ob, ""); + html_escape(ob, blob_buffer(text), blob_size(text)); + blob_append_literal(ob, ""); + }else{ + /* Three or more graves: a fenced code block */ + int n = blob_size(text); + const char *z = blob_buffer(text); + int i; + for(i=0; i=n ){ + blob_appendf(ob, "
        %#h
        ", n, z); + }else{ + int k, j; + i++; + for(k=0; k%#h", n-i, z+i); + }else{ + for(j=k+1; j%#h", + j-k, z+k, n-i, z+i); + } + } + } + } + return 1; +} + +static int html_double_emphasis( + struct Blob *ob, + struct Blob *text, + char c, + void *opaque +){ + blob_append_literal(ob, ""); + blob_appendb(ob, text); + blob_append_literal(ob, ""); + return 1; +} + +static int html_emphasis( + struct Blob *ob, + struct Blob *text, + char c, + void *opaque +){ + blob_append_literal(ob, ""); + blob_appendb(ob, text); + blob_append_literal(ob, ""); + return 1; +} + +static int html_image( + struct Blob *ob, + struct Blob *link, + struct Blob *title, + struct Blob *alt, + void *opaque +){ + blob_append_literal(ob, "\"");0 ){ + blob_append_literal(ob, "\" title=\""); + html_quote(ob, blob_buffer(title), blob_size(title)); + } + blob_append_literal(ob, "\">"); + return 1; +} + +static int html_linebreak(struct Blob *ob, void *opaque){ + blob_append_literal(ob, "
        \n"); + return 1; +} + +static int html_link( + struct Blob *ob, + struct Blob *link, + struct Blob *title, + struct Blob *content, + void *opaque +){ + char *zLink = blob_buffer(link); + char *zTitle = title!=0 && blob_size(title)>0 ? blob_str(title) : 0; + char zClose[20]; + + if( zLink==0 || zLink[0]==0 ){ + zClose[0] = 0; + }else{ + static const int flags = + WIKI_NOBADLINKS | + WIKI_MARKDOWNLINKS + ; + wiki_resolve_hyperlink(ob, flags, zLink, zClose, sizeof(zClose), 0, zTitle); + } + if( blob_size(content)==0 ){ + if( link ) blob_appendb(ob, link); + }else{ + blob_appendb(ob, content); + } + blob_append(ob, zClose, -1); + return 1; +} + +static int html_triple_emphasis( + struct Blob *ob, + struct Blob *text, + char c, + void *opaque +){ + blob_append_literal(ob, ""); + blob_appendb(ob, text); + blob_append_literal(ob, ""); + return 1; +} + + +static void html_normal_text(struct Blob *ob, struct Blob *text, void *opaque){ + html_escape(ob, blob_buffer(text), blob_size(text)); +} + +/* +** Convert markdown into HTML. +** +** The document title is placed in output_title if not NULL. Or if +** output_title is NULL, the document title appears in the body. +*/ +void markdown_to_html( + struct Blob *input_markdown, /* Markdown content to be rendered */ + struct Blob *output_title, /* Put title here. May be NULL */ + struct Blob *output_body /* Put document body here. */ +){ + struct mkd_renderer html_renderer = { + /* prolog and epilog */ + html_prolog, + html_epilog, + html_footnotes, + + /* block level elements */ + html_blockcode, + html_blockquote, + html_blockhtml, + html_header, + html_hrule, + html_list, + html_list_item, + html_paragraph, + html_table, + html_table_cell, + html_table_row, + html_footnote_item, + + /* span level elements */ + html_autolink, + html_codespan, + html_double_emphasis, + html_emphasis, + html_image, + html_linebreak, + html_link, + html_raw_html_tag, + html_triple_emphasis, + html_footnote_ref, + + /* low level elements */ + 0, /* entity */ + html_normal_text, + + /* misc. parameters */ + "*_", /* emph_chars */ + 0 /* opaque */ + }; + static int invocation = -1; /* no marker for the first document */ + static const char* zRU = 0; /* REQUEST_URI with escaped quotes */ + MarkdownToHtml context; + memset(&context, 0, sizeof(context)); + context.output_title = output_title; + context.unique = to_base26(invocation++,1); + if( !zRU ) zRU = escape_quotes(PD("REQUEST_URI","")); + #ifndef FOOTNOTES_WITHOUT_URI + blob_set( &context.reqURI, zRU ); + #endif + html_renderer.opaque = &context; + if( output_title ) blob_reset(output_title); + blob_reset(output_body); + markdown(output_body, input_markdown, &html_renderer); +} + +/* +** Undo HTML escapes in Blob p. In other words convert: +** +** & -> & +** < -> < +** > -> > +** " -> " +** &#NNN; -> ascii character NNN +*/ +void markdown_dehtmlize_blob(Blob *p){ + char *z; + unsigned int j, k; + + z = p->aData; + for(j=k=0; jnUsed; j++){ + char c = z[j]; + if( c=='&' ){ + if( z[j+1]=='#' && fossil_isdigit(z[j+2]) ){ + int n = 3; + int x = z[j+2] - '0'; + if( fossil_isdigit(z[j+3]) ){ + x = x*10 + z[j+3] - '0'; + n++; + if( fossil_isdigit(z[j+4]) ){ + x = x*10 + z[j+4] - '0'; + n++; + } + } + if( z[j+n]==';' ){ + z[k++] = (char)x; + j += n; + }else{ + z[k++] = c; + } + }else if( memcmp(&z[j],"<",4)==0 ){ + z[k++] = '<'; + j += 3; + }else if( memcmp(&z[j],">",4)==0 ){ + z[k++] = '>'; + j += 3; + }else if( memcmp(&z[j],""",6)==0 ){ + z[k++] = '"'; + j += 5; + }else if( memcmp(&z[j],"&",5)==0 ){ + z[k++] = '&'; + j += 4; + }else{ + z[k++] = c; + } + }else{ + z[k++] = c; + } + } + z[k] = 0; + p->nUsed = k; +} -- cgit v1.3.1