kicad/markdown2html/html_formatter/html.c

778 lines
17 KiB
C

/*
* Copyright (c) 2009, Natacha Porté
* Copyright (c) 2011, Vicent Marti
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "markdown.h"
#include "html.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include "houdini.h"
#define USE_XHTML( opt ) (opt->flags & HTML_USE_XHTML)
int sdhtml_is_tag( const uint8_t* tag_data, size_t tag_size, const char* tagname )
{
size_t i;
int closed = 0;
if( tag_size < 3 || tag_data[0] != '<' )
return HTML_TAG_NONE;
i = 1;
if( tag_data[i] == '/' )
{
closed = 1;
i++;
}
for( ; i < tag_size; ++i, ++tagname )
{
if( *tagname == 0 )
break;
if( tag_data[i] != *tagname )
return HTML_TAG_NONE;
}
if( i == tag_size )
return HTML_TAG_NONE;
if( isspace( tag_data[i] ) || tag_data[i] == '>' )
return closed ? HTML_TAG_CLOSE : HTML_TAG_OPEN;
return HTML_TAG_NONE;
}
static inline void escape_html( struct buf* ob, const uint8_t* source, size_t length )
{
houdini_escape_html0( ob, source, length, 0 );
}
static inline void escape_href( struct buf* ob, const uint8_t* source, size_t length )
{
houdini_escape_href( ob, source, length );
}
/********************
* GENERIC RENDERER *
********************/
static int rndr_autolink( struct buf* ob,
const struct buf* link,
enum mkd_autolink type,
void* opaque )
{
struct html_renderopt* options = opaque;
if( !link || !link->size )
return 0;
if( (options->flags & HTML_SAFELINK) != 0
&& !sd_autolink_issafe( link->data, link->size )
&& type != MKDA_EMAIL )
return 0;
BUFPUTSL( ob, "<a href=\"" );
if( type == MKDA_EMAIL )
BUFPUTSL( ob, "mailto:" );
escape_href( ob, link->data, link->size );
if( options->link_attributes )
{
bufputc( ob, '\"' );
options->link_attributes( ob, link, opaque );
bufputc( ob, '>' );
}
else
{
BUFPUTSL( ob, "\">" );
}
/*
* Pretty printing: if we get an email address as
* an actual URI, e.g. `mailto:foo@bar.com`, we don't
* want to print the `mailto:` prefix
*/
if( bufprefix( link, "mailto:" ) == 0 )
{
escape_html( ob, link->data + 7, link->size - 7 );
}
else
{
escape_html( ob, link->data, link->size );
}
BUFPUTSL( ob, "</a>" );
return 1;
}
static void rndr_blockcode( struct buf* ob,
const struct buf* text,
const struct buf* lang,
void* opaque )
{
if( ob->size )
bufputc( ob, '\n' );
if( lang && lang->size )
{
size_t i, cls;
BUFPUTSL( ob, "<pre><code class=\"" );
for( i = 0, cls = 0; i < lang->size; ++i, ++cls )
{
while( i < lang->size && isspace( lang->data[i] ) )
i++;
if( i < lang->size )
{
size_t org = i;
while( i < lang->size && !isspace( lang->data[i] ) )
i++;
if( lang->data[org] == '.' )
org++;
if( cls )
bufputc( ob, ' ' );
escape_html( ob, lang->data + org, i - org );
}
}
BUFPUTSL( ob, "\">" );
}
else
BUFPUTSL( ob, "<pre><code>" );
if( text )
escape_html( ob, text->data, text->size );
BUFPUTSL( ob, "</code></pre>\n" );
}
static void rndr_blockquote( struct buf* ob, const struct buf* text, void* opaque )
{
if( ob->size )
bufputc( ob, '\n' );
BUFPUTSL( ob, "<blockquote>\n" );
if( text )
bufput( ob, text->data, text->size );
BUFPUTSL( ob, "</blockquote>\n" );
}
static int rndr_codespan( struct buf* ob, const struct buf* text, void* opaque )
{
BUFPUTSL( ob, "<code>" );
if( text )
escape_html( ob, text->data, text->size );
BUFPUTSL( ob, "</code>" );
return 1;
}
static int rndr_strikethrough( struct buf* ob, const struct buf* text, void* opaque )
{
if( !text || !text->size )
return 0;
BUFPUTSL( ob, "<del>" );
bufput( ob, text->data, text->size );
BUFPUTSL( ob, "</del>" );
return 1;
}
static int rndr_double_emphasis( struct buf* ob, const struct buf* text, void* opaque )
{
if( !text || !text->size )
return 0;
BUFPUTSL( ob, "<strong>" );
bufput( ob, text->data, text->size );
BUFPUTSL( ob, "</strong>" );
return 1;
}
static int rndr_emphasis( struct buf* ob, const struct buf* text, void* opaque )
{
if( !text || !text->size )
return 0;
BUFPUTSL( ob, "<em>" );
if( text )
bufput( ob, text->data, text->size );
BUFPUTSL( ob, "</em>" );
return 1;
}
static int rndr_linebreak( struct buf* ob, void* opaque )
{
struct html_renderopt* options = opaque;
bufputs( ob, USE_XHTML( options ) ? "<br/>\n" : "<br>\n" );
return 1;
}
static void rndr_header( struct buf* ob, const struct buf* text, int level, void* opaque )
{
struct html_renderopt* options = opaque;
if( ob->size )
bufputc( ob, '\n' );
if( options->flags & HTML_TOC )
bufprintf( ob, "<h%d id=\"toc_%d\">", level, options->toc_data.header_count++ );
else
bufprintf( ob, "<h%d>", level );
if( text )
bufput( ob, text->data, text->size );
bufprintf( ob, "</h%d>\n", level );
}
static int rndr_link( struct buf* ob,
const struct buf* link,
const struct buf* title,
const struct buf* content,
void* opaque )
{
struct html_renderopt* options = opaque;
if( link != NULL && (options->flags & HTML_SAFELINK) != 0
&& !sd_autolink_issafe( link->data, link->size ) )
return 0;
BUFPUTSL( ob, "<a href=\"" );
if( link && link->size )
escape_href( ob, link->data, link->size );
if( title && title->size )
{
BUFPUTSL( ob, "\" title=\"" );
escape_html( ob, title->data, title->size );
}
if( options->link_attributes )
{
bufputc( ob, '\"' );
options->link_attributes( ob, link, opaque );
bufputc( ob, '>' );
}
else
{
BUFPUTSL( ob, "\">" );
}
if( content && content->size )
bufput( ob, content->data, content->size );
BUFPUTSL( ob, "</a>" );
return 1;
}
static void rndr_list( struct buf* ob, const struct buf* text, int flags, void* opaque )
{
if( ob->size )
bufputc( ob, '\n' );
bufput( ob, flags & MKD_LIST_ORDERED ? "<ol>\n" : "<ul>\n", 5 );
if( text )
bufput( ob, text->data, text->size );
bufput( ob, flags & MKD_LIST_ORDERED ? "</ol>\n" : "</ul>\n", 6 );
}
static void rndr_listitem( struct buf* ob, const struct buf* text, int flags, void* opaque )
{
BUFPUTSL( ob, "<li>" );
if( text )
{
size_t size = text->size;
while( size && text->data[size - 1] == '\n' )
size--;
bufput( ob, text->data, size );
}
BUFPUTSL( ob, "</li>\n" );
}
static void rndr_paragraph( struct buf* ob, const struct buf* text, void* opaque )
{
struct html_renderopt* options = opaque;
size_t i = 0;
if( ob->size )
bufputc( ob, '\n' );
if( !text || !text->size )
return;
while( i < text->size && isspace( text->data[i] ) )
i++;
if( i == text->size )
return;
BUFPUTSL( ob, "<p>" );
if( options->flags & HTML_HARD_WRAP )
{
size_t org;
while( i < text->size )
{
org = i;
while( i < text->size && text->data[i] != '\n' )
i++;
if( i > org )
bufput( ob, text->data + org, i - org );
/*
* do not insert a line break if this newline
* is the last character on the paragraph
*/
if( i >= text->size - 1 )
break;
rndr_linebreak( ob, opaque );
i++;
}
}
else
{
bufput( ob, &text->data[i], text->size - i );
}
BUFPUTSL( ob, "</p>\n" );
}
static void rndr_raw_block( struct buf* ob, const struct buf* text, void* opaque )
{
size_t org, sz;
if( !text )
return;
sz = text->size;
while( sz > 0 && text->data[sz - 1] == '\n' )
sz--;
org = 0;
while( org < sz && text->data[org] == '\n' )
org++;
if( org >= sz )
return;
if( ob->size )
bufputc( ob, '\n' );
bufput( ob, text->data + org, sz - org );
bufputc( ob, '\n' );
}
static int rndr_triple_emphasis( struct buf* ob, const struct buf* text, void* opaque )
{
if( !text || !text->size )
return 0;
BUFPUTSL( ob, "<strong><em>" );
bufput( ob, text->data, text->size );
BUFPUTSL( ob, "</em></strong>" );
return 1;
}
static void rndr_hrule( struct buf* ob, void* opaque )
{
struct html_renderopt* options = opaque;
if( ob->size )
bufputc( ob, '\n' );
bufputs( ob, USE_XHTML( options ) ? "<hr/>\n" : "<hr>\n" );
}
static int rndr_image( struct buf* ob,
const struct buf* link,
const struct buf* title,
const struct buf* alt,
void* opaque )
{
struct html_renderopt* options = opaque;
if( !link || !link->size )
return 0;
BUFPUTSL( ob, "<img src=\"" );
escape_href( ob, link->data, link->size );
BUFPUTSL( ob, "\" alt=\"" );
if( alt && alt->size )
escape_html( ob, alt->data, alt->size );
if( title && title->size )
{
BUFPUTSL( ob, "\" title=\"" );
escape_html( ob, title->data, title->size );
}
bufputs( ob, USE_XHTML( options ) ? "\"/>" : "\">" );
return 1;
}
static int rndr_raw_html( struct buf* ob, const struct buf* text, void* opaque )
{
struct html_renderopt* options = opaque;
/* HTML_ESCAPE overrides SKIP_HTML, SKIP_STYLE, SKIP_LINKS and SKIP_IMAGES
* It doens't see if there are any valid tags, just escape all of them. */
if( (options->flags & HTML_ESCAPE) != 0 )
{
escape_html( ob, text->data, text->size );
return 1;
}
if( (options->flags & HTML_SKIP_HTML) != 0 )
return 1;
if( (options->flags & HTML_SKIP_STYLE) != 0
&& sdhtml_is_tag( text->data, text->size, "style" ) )
return 1;
if( (options->flags & HTML_SKIP_LINKS) != 0
&& sdhtml_is_tag( text->data, text->size, "a" ) )
return 1;
if( (options->flags & HTML_SKIP_IMAGES) != 0
&& sdhtml_is_tag( text->data, text->size, "img" ) )
return 1;
bufput( ob, text->data, text->size );
return 1;
}
static void rndr_table( struct buf* ob,
const struct buf* header,
const struct buf* body,
void* opaque )
{
if( ob->size )
bufputc( ob, '\n' );
BUFPUTSL( ob, "<table><thead>\n" );
if( header )
bufput( ob, header->data, header->size );
BUFPUTSL( ob, "</thead><tbody>\n" );
if( body )
bufput( ob, body->data, body->size );
BUFPUTSL( ob, "</tbody></table>\n" );
}
static void rndr_tablerow( struct buf* ob, const struct buf* text, void* opaque )
{
BUFPUTSL( ob, "<tr>\n" );
if( text )
bufput( ob, text->data, text->size );
BUFPUTSL( ob, "</tr>\n" );
}
static void rndr_tablecell( struct buf* ob, const struct buf* text, int flags, void* opaque )
{
if( flags & MKD_TABLE_HEADER )
{
BUFPUTSL( ob, "<th" );
}
else
{
BUFPUTSL( ob, "<td" );
}
switch( flags & MKD_TABLE_ALIGNMASK )
{
case MKD_TABLE_ALIGN_CENTER:
BUFPUTSL( ob, " align=\"center\">" );
break;
case MKD_TABLE_ALIGN_L:
BUFPUTSL( ob, " align=\"left\">" );
break;
case MKD_TABLE_ALIGN_R:
BUFPUTSL( ob, " align=\"right\">" );
break;
default:
BUFPUTSL( ob, ">" );
}
if( text )
bufput( ob, text->data, text->size );
if( flags & MKD_TABLE_HEADER )
{
BUFPUTSL( ob, "</th>\n" );
}
else
{
BUFPUTSL( ob, "</td>\n" );
}
}
static int rndr_superscript( struct buf* ob, const struct buf* text, void* opaque )
{
if( !text || !text->size )
return 0;
BUFPUTSL( ob, "<sup>" );
bufput( ob, text->data, text->size );
BUFPUTSL( ob, "</sup>" );
return 1;
}
static void rndr_normal_text( struct buf* ob, const struct buf* text, void* opaque )
{
if( text )
escape_html( ob, text->data, text->size );
}
static void toc_header( struct buf* ob, const struct buf* text, int level, void* opaque )
{
struct html_renderopt* options = opaque;
/* set the level offset if this is the first header
* we're parsing for the document */
if( options->toc_data.current_level == 0 )
{
options->toc_data.level_offset = level - 1;
}
level -= options->toc_data.level_offset;
if( level > options->toc_data.current_level )
{
while( level > options->toc_data.current_level )
{
BUFPUTSL( ob, "<ul>\n<li>\n" );
options->toc_data.current_level++;
}
}
else if( level < options->toc_data.current_level )
{
BUFPUTSL( ob, "</li>\n" );
while( level < options->toc_data.current_level )
{
BUFPUTSL( ob, "</ul>\n</li>\n" );
options->toc_data.current_level--;
}
BUFPUTSL( ob, "<li>\n" );
}
else
{
BUFPUTSL( ob, "</li>\n<li>\n" );
}
bufprintf( ob, "<a href=\"#toc_%d\">", options->toc_data.header_count++ );
if( text )
escape_html( ob, text->data, text->size );
BUFPUTSL( ob, "</a>\n" );
}
static int toc_link( struct buf* ob,
const struct buf* link,
const struct buf* title,
const struct buf* content,
void* opaque )
{
if( content && content->size )
bufput( ob, content->data, content->size );
return 1;
}
static void toc_finalize( struct buf* ob, void* opaque )
{
struct html_renderopt* options = opaque;
while( options->toc_data.current_level > 0 )
{
BUFPUTSL( ob, "</li>\n</ul>\n" );
options->toc_data.current_level--;
}
}
void sdhtml_toc_renderer( struct sd_callbacks* callbacks, struct html_renderopt* options )
{
static const struct sd_callbacks cb_default =
{
NULL,
NULL,
NULL,
toc_header,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
rndr_codespan,
rndr_double_emphasis,
rndr_emphasis,
NULL,
NULL,
toc_link,
NULL,
rndr_triple_emphasis,
rndr_strikethrough,
rndr_superscript,
NULL,
NULL,
NULL,
toc_finalize,
};
memset( options, 0x0, sizeof(struct html_renderopt) );
options->flags = HTML_TOC;
memcpy( callbacks, &cb_default, sizeof(struct sd_callbacks) );
}
void sdhtml_renderer( struct sd_callbacks* callbacks,
struct html_renderopt* options,
unsigned int render_flags )
{
static const struct sd_callbacks cb_default =
{
rndr_blockcode,
rndr_blockquote,
rndr_raw_block,
rndr_header,
rndr_hrule,
rndr_list,
rndr_listitem,
rndr_paragraph,
rndr_table,
rndr_tablerow,
rndr_tablecell,
rndr_autolink,
rndr_codespan,
rndr_double_emphasis,
rndr_emphasis,
rndr_image,
rndr_linebreak,
rndr_link,
rndr_raw_html,
rndr_triple_emphasis,
rndr_strikethrough,
rndr_superscript,
NULL,
rndr_normal_text,
NULL,
NULL,
};
/* Prepare the options pointer */
memset( options, 0x0, sizeof(struct html_renderopt) );
options->flags = render_flags;
/* Prepare the callbacks */
memcpy( callbacks, &cb_default, sizeof(struct sd_callbacks) );
if( render_flags & HTML_SKIP_IMAGES )
callbacks->image = NULL;
if( render_flags & HTML_SKIP_LINKS )
{
callbacks->link = NULL;
callbacks->autolink = NULL;
}
if( render_flags & HTML_SKIP_HTML || render_flags & HTML_ESCAPE )
callbacks->blockhtml = NULL;
}