/* * md4c modified for markdown-wasm. * Original source code is licensed as follows: * * Copyright (c) 2016-2019 Martin Mitas * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include "common.h" #include "fmt_html.h" #include "md4c.h" typedef struct HtmlRenderer_st { WBuf* outbuf; int imgnest; int addanchor; u32 flags; } HtmlRenderer; static char htmlEscapeMap[256] = { /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ /* 0x00 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // ... /* 0x10 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // ... /* 0x20 */ 0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0, // ! " # $ % & ' ( ) * + , - . / /* 0x30 */ 0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0, // 0 1 2 3 4 5 6 7 8 9 : ; < = > ? /* 0x40 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // @ A B C D E F G H I J K L M N O /* 0x50 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // P Q R S T U V W X Y Z [ \ ] ^ _ /* 0x60 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // ` a b c d e f g h i j k l m n o /* 0x70 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // p q r s t u v w x y z { | } ~ /* 0x80 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // ... /* 0x90 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // ... /* 0xA0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // ¡ ¢ £ ¤ ¥ ¦ § ¨ © ª « ¬ ® ¯ /* 0xB0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // ° ± ² ³ ´ µ ¶ · ¸ ¹ º » ¼ ½ ¾ ¿ /* 0xC0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï /* 0xD0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // Ð Ñ Ò Ó Ô Õ Ö × Ø Ù Ú Û Ü Ý Þ ß /* 0xE0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // à á â ã ä å æ ç è é ê ë ì í î ï /* 0xF0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // ð ñ ò ó ô õ ö ÷ ø ù ú û ü ý þ ÿ }; static const char ucReplacementUTF8[] = { 0xef, 0xbf, 0xbd }; static inline void render_text(HtmlRenderer* r, const char* pch, size_t len) { WBufAppendBytes(r->outbuf, pch, len); } static inline void render_literal(HtmlRenderer* r, const char* cs) { WBufAppendBytes(r->outbuf, cs, strlen(cs)); } static inline void render_char(HtmlRenderer* r, char c) { WBufAppendc(r->outbuf, c); } static void render_html_escaped(HtmlRenderer* r, const char* data, size_t size) { MD_OFFSET beg = 0; MD_OFFSET off = 0; /* Some characters need to be escaped in normal HTML text. */ #define HTML_NEED_ESCAPE(ch) (htmlEscapeMap[(unsigned char)(ch)] != 0) while (1) { while ( off + 3 < size && !HTML_NEED_ESCAPE(data[off+0]) && !HTML_NEED_ESCAPE(data[off+1]) && !HTML_NEED_ESCAPE(data[off+2]) && !HTML_NEED_ESCAPE(data[off+3]) ) { off += 4; } while (off < size && !HTML_NEED_ESCAPE(data[off])) { off++; } if (off > beg) { render_text(r, data + beg, off - beg); } if (off < size) { switch (data[off]) { case '&': render_literal(r, "&"); break; case '<': render_literal(r, "<"); break; case '>': render_literal(r, ">"); break; case '"': render_literal(r, """); break; } off++; } else { break; } beg = off; } } static char slugMap[256] = { /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ /* 0x00 */ '-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-', // ... /* 0x10 */ '-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-', // ... /* 0x20 */ '-','-','-','-','-','-','-','-','-','-','-','-','-','-','.','-', // ! " # $ % & ' ( ) * + , - . / /* 0x30 */ '0','1','2','3','4','5','6','7','8','9','-','-','-','-','-','-', // 0 1 2 3 4 5 6 7 8 9 : ; < = > ? /* 0x40 */ '-','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o', // @ A B C D E F G H I J K L M N O /* 0x50 */ 'p','q','r','s','t','u','v','w','x','y','z','-','-','-','-','_', // P Q R S T U V W X Y Z [ \ ] ^ _ /* 0x60 */ '-','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o', // ` a b c d e f g h i j k l m n o /* 0x70 */ 'p','q','r','s','t','u','v','w','x','y','z','-','-','-','-','-', // p q r s t u v w x y z { | } ~ /* 0x80 */ '-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-', // ... /* 0x90 */ '-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-', // ... /* 0xA0 */ '-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-', // ¡ ¢ £ ¤ ¥ ¦ § ¨ © ª « ¬ ® ¯ /* 0xB0 */ '-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-', // ° ± ² ³ ´ µ ¶ · ¸ ¹ º » ¼ ½ ¾ ¿ /* 0xC0 */ 'a','a','a','a','a','a','a','c','e','e','e','e','i','i','i','i', // À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï /* 0xD0 */ 'd','n','o','o','o','o','o','x','o','u','u','u','u','y','-','s', // Ð Ñ Ò Ó Ô Õ Ö × Ø Ù Ú Û Ü Ý Þ ß /* 0xE0 */ 'a','a','a','a','a','a','a','c','e','e','e','e','i','i','i','i', // à á â ã ä å æ ç è é ê ë ì í î ï /* 0xF0 */ 'd','n','o','o','o','o','o','-','o','u','u','u','u','y','-','y', // ð ñ ò ó ô õ ö ÷ ø ù ú û ü ý þ ÿ }; static size_t WBufAppendSlug(WBuf* b, const char* pch, size_t len) { WBufReserve(b, len); const char* start = b->ptr; char c = 0, pc = 0; for (size_t i = 0; i < len; i++) { u8 x = (u8)pch[i]; if (x >= 0x80) { // decode UTF8-encoded character as Latin-1 if ((x >> 5) == 0x6 && i+1 < len) { u32 cp = ((x << 6) & 0x7ff) + ((pch[++i]) & 0x3f); x = cp <= 0xFF ? cp : 0; } else { x = 0; } } c = slugMap[x]; if (c != '-' || (pc != '-' && pc)) { // note: check "pc" to trim leading '-' *(b->ptr++) = c; pc = c; } } if (pc == '-') { // trim trailing '-' b->ptr--; } return b->ptr - start; } static void render_attribute(HtmlRenderer* r, const MD_ATTRIBUTE* attr) { int i; for (i = 0; attr->substr_offsets[i] < attr->size; i++) { MD_TEXTTYPE type = attr->substr_types[i]; MD_OFFSET off = attr->substr_offsets[i]; MD_SIZE size = attr->substr_offsets[i+1] - off; const MD_CHAR* text = attr->text + off; switch (type) { case MD_TEXT_NULLCHAR: render_text(r, ucReplacementUTF8, sizeof(ucReplacementUTF8)); break; case MD_TEXT_ENTITY: render_text(r, text, size); break; default: render_html_escaped(r, text, size); break; } } } static void render_open_ol_block(HtmlRenderer* r, const MD_BLOCK_OL_DETAIL* det) { if (det->start == 1) { render_literal(r, "
    \n"); } else { render_literal(r, "
      outbuf, det->start, 10); render_literal(r, "\">\n"); } } static void render_open_li_block(HtmlRenderer* r, const MD_BLOCK_LI_DETAIL* det) { if (det->is_task) { render_literal(r, "
    1. task_mark == 'x' || det->task_mark == 'X') { render_literal(r, " checked"); } render_char(r, '>'); } else { render_literal(r, "
    2. "); } } static void render_open_code_block(HtmlRenderer* r, const MD_BLOCK_CODE_DETAIL* det) { render_literal(r, "
      lang.text != NULL) {
          render_literal(r, " class=\"language-");
          render_attribute(r, &det->lang);
          render_char(r, '"');
        }
        render_char(r, '>');
      }
      
      static void render_open_td_block(HtmlRenderer* r, bool isTH, const MD_BLOCK_TD_DETAIL* det) {
        render_text(r, isTH ? "align) {
          case MD_ALIGN_LEFT:   render_literal(r, " align=\"left\">"); break;
          case MD_ALIGN_CENTER: render_literal(r, " align=\"center\">"); break;
          case MD_ALIGN_RIGHT:  render_literal(r, " align=\"right\">"); break;
          default:              render_char(r, '>'); break;
        }
      }
      
      static void render_open_a_span(HtmlRenderer* r, const MD_SPAN_A_DETAIL* det) {
        render_literal(r, "href);
        if (det->title.text != NULL) {
          render_literal(r, "\" title=\"");
          render_attribute(r, &det->title);
        }
        render_literal(r, "\">");
      }
      
      static void render_open_img_span(HtmlRenderer* r, const MD_SPAN_IMG_DETAIL* det) {
        render_literal(r, "src);
        render_literal(r, "\" alt=\"");
        r->imgnest++;
      }
      
      static void render_close_img_span(HtmlRenderer* r, const MD_SPAN_IMG_DETAIL* det) {
        if(det->title.text != NULL) {
          render_literal(r, "\" title=\"");
          render_attribute(r, &det->title);
        }
        render_literal(r, (r->flags & MD_HTML_FLAG_XHTML) ? "\"/>" : "\">");
        r->imgnest--;
      }
      
      static void render_open_wikilink_span(HtmlRenderer* r, const MD_SPAN_WIKILINK_DETAIL* det) {
        render_literal(r, "target);
        render_literal(r, "\">");
      }
      
      
      /**************************************
       ***  HTML renderer implementation  ***
       **************************************/
      
      
      
      static int enter_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata) {
        static const MD_CHAR* head[6] = { "

      ", "

      ", "

      ", "

      ", "

      ", "
      " }; HtmlRenderer* r = (HtmlRenderer*) userdata; switch(type) { case MD_BLOCK_DOC: /* noop */ break; case MD_BLOCK_QUOTE: render_literal(r, "
      \n"); break; case MD_BLOCK_UL: render_literal(r, "