Browse Source

Added custom code block highlighting support.

Brucey 2 years ago
parent
commit
795cee2d5c

+ 52 - 0
markdown.mod/examples/example_03.bmx

@@ -0,0 +1,52 @@
+SuperStrict
+
+Framework brl.standardio
+Import text.markdown
+Import text.pikchr
+
+Local sb:TStringBuilder = New TStringBuilder
+Local highlighter:THighlighter = New THighlighter
+
+sb.Append("<!DOCTYPE html><html><head><meta charset=~qutf-8~q><title>Markdown</title>")
+sb.Append("<body>")
+
+TMarkdown.ParseToHtml("""
+Hello !
+```pikchr
+arrow right 200% "Markdown" "Source"
+box rad 10px "Markdown" "Formatter" "(markdown.c)" fit
+arrow right 200% "HTML+SVG" "Output"
+arrow <-> down 70% from last box.s
+box same "Pikchr" "Formatter" "(pikchr.c)" fit
+```
+hmm...
+```csharp
+Console.WriteLine("Hello World!");
+```
+World!
+""", sb,,,,highlighter)
+
+sb.Append("</body></html>")
+
+SaveString(sb.ToString(), "markdown.html")
+
+
+Type THighlighter Extends TMDHtmlCodeHighlighter
+
+	Method Text:Int(lang:String, txt:String, output:TStringBuilder)
+
+		If lang = "pikchr" Then
+			Local width:Int, height:Int
+			Local out:String = Pikchr(txt, Null, EPikChrFlags.NONE, width, height)
+			
+			output.Append("<div style=~qmax-width:").Append(width).Append("px~q>")
+			output.Append(out)
+			output.Append("</div>")
+
+			Return True
+		End If
+
+		Return False
+	End Method
+
+End Type

+ 36 - 3
markdown.mod/glue.c

@@ -20,6 +20,7 @@
   THE SOFTWARE.
 */ 
 #include "md4c.h"
+#include "md4c-html.h"
 #include "brl.mod/blitz.mod/blitz.h"
 
 
@@ -29,6 +30,10 @@ int text_markdown_TMarkdown__EnterSpan(BBObject * obj, MD_SPANTYPE type, void *
 int text_markdown_TMarkdown__LeaveSpan(BBObject * obj, MD_SPANTYPE type, void * detail);
 int text_markdown_TMarkdown__Text(BBObject * obj, MD_TEXTTYPE type, BBString * text);
 void text_markdown_TMarkdown__HtmlOutput(const MD_CHAR * txt, MD_SIZE size, BBObject * ob );
+int text_markdown_TMarkdown__EnterCodeBlock(BBObject * obj, MD_BLOCKTYPE type, void * detail);
+int text_markdown_TMarkdown__LeaveCodeBlock(BBObject * obj, MD_BLOCKTYPE type, void * detail);
+int text_markdown_TMarkdown__CodeBlockText(BBObject * obj, MD_TEXTTYPE type, BBString * text);
+
 
 int bmx_md_cb_enter_block(MD_BLOCKTYPE type, void* detail, void* userdata) {
     return text_markdown_TMarkdown__EnterBlock((BBObject *)userdata, type, detail);
@@ -55,6 +60,19 @@ void bmx_md_cb_html_output(const MD_CHAR * txt, MD_SIZE size, void * userdata) {
     text_markdown_TMarkdown__HtmlOutput(txt, size, (BBObject*)userdata);
 }
 
+int bmx_md_cb_enter_codeblock(MD_BLOCKTYPE type, void* detail, void* userdata) {
+    return text_markdown_TMarkdown__EnterCodeBlock((BBObject *)userdata, type, detail);
+}
+
+int bmx_md_cb_leave_codeblock(MD_BLOCKTYPE type, void* detail, void* userdata) {
+    return text_markdown_TMarkdown__LeaveCodeBlock((BBObject *)userdata, type, detail);
+}
+
+int bmx_md_cb_codeblock_text(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, void* userdata) {
+    BBString * txt = bbStringFromUTF8Bytes((unsigned char*)text, size);
+    return text_markdown_TMarkdown__CodeBlockText((BBObject *)userdata, type, txt);
+}
+
 int bmx_md_parse(BBObject * obj, BBString * txt, int flags) {
 
     MD_PARSER parser = {
@@ -79,7 +97,7 @@ int bmx_md_parse(BBObject * obj, BBString * txt, int flags) {
     return res;
 }
 
-int bmx_md_html(BBString * text, BBObject * output, int parserFlags, int rendererFlags, int depth, char * ph) {
+int bmx_md_html(BBString * text, BBObject * output, int parserFlags, int rendererFlags, int depth, char * ph, BBObject * codeHilite) {
 
     MD_TOC_OPTIONS tocOptions = {
         depth,
@@ -89,8 +107,23 @@ int bmx_md_html(BBString * text, BBObject * output, int parserFlags, int rendere
     char * t = bbStringToUTF8String(text);
     MD_SIZE size = strlen(t);
 
-    int res = md_html(t, size, bmx_md_cb_html_output, output, parserFlags, rendererFlags, &tocOptions);
-
+    int res;
+    
+    if (codeHilite == &bbNullObject) {
+        res = md_html(t, size, bmx_md_cb_html_output, output, parserFlags, rendererFlags, &tocOptions, 0);
+    } else {
+
+        MD_HTML_CODE_HILITE hilite = {
+            codeHilite,
+            0,
+            bmx_md_cb_enter_codeblock,
+            bmx_md_cb_leave_codeblock,
+            bmx_md_cb_codeblock_text
+        };
+
+        res = md_html(t, size, bmx_md_cb_html_output, output, parserFlags, rendererFlags, &tocOptions, &hilite);
+    }
+   
     bbMemFree(t);
 
     return res;

+ 75 - 5
markdown.mod/markdown.bmx

@@ -20,12 +20,14 @@
 ' 
 SuperStrict
 
-ModuleInfo "Version: 1.00"
+ModuleInfo "Version: 1.01"
 ModuleInfo "Author: Bruce A Henderson"
 ModuleInfo "License: MIT"
-ModuleInfo "md4c - Copyright (c) Martin Mitas - https://github.com/tim-gromeyer/MarkdownEdit_md4c"
+ModuleInfo "md4c - Copyright (c) Martin Mitas - https://github.com/woollybah/MarkdownEdit_md4c"
 ModuleInfo "Copyright: 2023 Bruce A Henderson"
 
+ModuleInfo "History: 1.01"
+ModuleInfo "History: Added TMDHtmlCodeHighlighter for code highlighting."
 ModuleInfo "History: 1.00"
 ModuleInfo "History: Initial Release"
 
@@ -48,6 +50,53 @@ Interface IMDRenderer
 	Method Text:Int(text:String, textType:EMDTextType)
 End Interface
 
+Rem
+bbdoc: 
+End Rem
+Type TMDHtmlCodeHighlighter Abstract
+
+	Private
+	Field _codeblock:TStringBuilder
+	Field _output:TStringBuilder
+	Field _lang:String
+
+	Method _EnterCodeBlock:Int(block:TMDBlockCode)
+		_codeblock = New TStringBuilder
+		Local lang:SMDAttribute = block.Lang()
+		If lang.size > 0 Then
+			_lang = String.FromUTF8Bytes(lang.text, lang.size)
+		End If
+		Return 0
+	End Method
+
+	Method _LeaveCodeBlock:Int(block:TMDBlockCode)
+		Local processed:Int = Text(_lang, _codeblock.ToString(), _output)
+		If Not processed Then
+			_output.Append("<pre><code")
+			If _lang Then
+				_output.Append(" class=~qlanguage-").Append(_lang).Append("~q")
+			End If
+			_output.Append(">")
+			_output.Append(_codeblock.ToString()) ' TODO - escape
+			_output.Append("</code></pre>")
+		End If
+	End Method
+
+	Method _CodeBlockText:Int(txt:String, textType:EMDTextType)
+		_codeblock.Append(txt)
+	End Method
+
+	Public
+
+	Rem
+	bbdoc: Provides the text for a code block.
+	returns: #True if the code was processed, #False if the default code block rendering should be used.
+	about: If the code is processed, the output should be appended to @output.
+	End Rem
+	Method Text:Int(lang:String, text:String, output:TStringBuilder) Abstract
+
+End Type
+
 Rem
 bbdoc: Html table of contents options.
 End Rem
@@ -71,14 +120,20 @@ Type TMarkdown
 	Rem
 	bbdoc: Parses markdown @text, appending HTML into @output.
 	End Rem
-	Function ParseToHtml:Int(text:String, output:TStringBuilder, parserFlags:EMDFlags = EMDFlags.DIALECT_COMMONMARK, rendererFlags:EMDHtmlFlags = EMDHtmlFlags.NONE, tocOptions:TMDHtmlTocOptions = Null)
+	Function ParseToHtml:Int(text:String, output:TStringBuilder, parserFlags:EMDFlags = EMDFlags.DIALECT_COMMONMARK,..
+				rendererFlags:EMDHtmlFlags = EMDHtmlFlags.NONE,
+				tocOptions:TMDHtmlTocOptions = Null,
+				codehilite:TMDHtmlCodeHighlighter = Null)
 		Local depth:Int = 0
 		Local ph:Byte Ptr
 		If tocOptions Then
 			depth = tocOptions.depth
 			ph = tocOptions.placeHolder.ToUTF8String()
 		End If
-		Local res:Int = bmx_md_html(text, output, parserFlags, rendererFlags, depth, ph)
+		if codehilite Then
+			codehilite._output = output
+		End If
+		Local res:Int = bmx_md_html(text, output, parserFlags, rendererFlags, depth, ph, codehilite)
 		MemFree(ph)
 		Return res
 	End Function
@@ -112,6 +167,20 @@ Type TMarkdown
 		Return parser.Text(text, textType)
 	End Function
 
+	Function _EnterCodeBlock:Int(hilite:TMDHtmlCodeHighlighter, blockType:EMDBlockType, detail:Byte Ptr) { nomangle }
+		Local block:TMDBlockCode = TMDBlockCode(BlockAs(blockType, detail))
+		Return hilite._EnterCodeBlock(block)
+	End Function
+
+	Function _LeaveCodeBlock:Int(hilite:TMDHtmlCodeHighlighter, blockType:EMDBlockType, detail:Byte Ptr) { nomangle }
+		Local block:TMDBlockCode = TMDBlockCode(BlockAs(blockType, detail))
+		Return hilite._LeaveCodeBlock(block)
+	End Function
+
+	Function _CodeBlockText:Int(hilite:TMDHtmlCodeHighlighter, textType:EMDTextType, text:String) { nomangle }
+		Return hilite._CodeBlockText(text, textType)
+	End Function
+
 	Function BlockAs:TMDBlock(blockType:EMDBlockType, detail:Byte Ptr)
 		Select blockType
 			Case EMDBlockType.BLOCK_DOC
@@ -705,7 +774,8 @@ Private
 
 Extern
 	Function bmx_md_parse:Int(parser:IMDRenderer, text:String, flags:EMDFlags)
-	Function bmx_md_html:Int(text:String, output:TStringBuilder, parserFlags:EMDFlags, rendererFlags:EMDHtmlFlags, depth:Int, ph:Byte Ptr)
+	Function bmx_md_html:Int(text:String, output:TStringBuilder, parserFlags:EMDFlags, ..
+		rendererFlags:EMDHtmlFlags, depth:Int, ph:Byte Ptr, ch:Object)
 
 	Function bmx_md_blockul_istight:Int(detail:Byte Ptr)
 	Function bmx_md_blockul_mark:Int(detail:Byte Ptr)

+ 30 - 4
markdown.mod/md4c/src/md4c-html.c

@@ -53,6 +53,7 @@ struct MD_HTML_tag {
     void* userdata;
     unsigned flags;
     int image_nesting_level;
+    MD_HTML_CODE_HILITE* code_hilite;
     char escape_map[256];
 };
 
@@ -402,7 +403,15 @@ enter_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
         case MD_BLOCK_LI:       render_open_li_block(r, (const MD_BLOCK_LI_DETAIL*)detail); break;
         case MD_BLOCK_HR:       RENDER_VERBATIM(r, (r->flags & MD_HTML_FLAG_XHTML) ? "<hr />\n" : "<hr>\n"); break;
         case MD_BLOCK_H:        render_header_block(r, (const MD_BLOCK_H_DETAIL*)detail); break;
-        case MD_BLOCK_CODE:     render_open_code_block(r, (const MD_BLOCK_CODE_DETAIL*) detail); break;
+        case MD_BLOCK_CODE:     {
+                                    if (r->code_hilite) {
+                                        r->code_hilite->in_code_block = 1;
+                                        r->code_hilite->enter_block(MD_BLOCK_CODE, detail, r->code_hilite->userdata);
+                                    } else {
+                                        render_open_code_block(r, (const MD_BLOCK_CODE_DETAIL*) detail);
+                                    }       
+                                    break; 
+                                }
         case MD_BLOCK_HTML:     /* noop */ break;
         case MD_BLOCK_P:        RENDER_VERBATIM(r, "<p>"); break;
         case MD_BLOCK_TABLE:    RENDER_VERBATIM(r, "<table>\n"); break;
@@ -431,7 +440,15 @@ leave_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
         case MD_BLOCK_LI:       RENDER_VERBATIM(r, "</li>\n"); break;
         case MD_BLOCK_HR:       /*noop*/ break;
         case MD_BLOCK_H:        RENDER_VERBATIM(r, head[((MD_BLOCK_H_DETAIL*)detail)->level - 1]); break;
-        case MD_BLOCK_CODE:     RENDER_VERBATIM(r, "</code></pre>\n"); break;
+        case MD_BLOCK_CODE:     {
+                                    if (r->code_hilite) {
+                                        r->code_hilite->in_code_block = 0;
+                                        r->code_hilite->leave_block(type, detail, r->code_hilite->userdata);
+                                    } else {
+                                        RENDER_VERBATIM(r, "</code></pre>\n");
+                                    }       
+                                    break; 
+                                }
         case MD_BLOCK_HTML:     /* noop */ break;
         case MD_BLOCK_P:        RENDER_VERBATIM(r, "</p>\n"); break;
         case MD_BLOCK_TABLE:    RENDER_VERBATIM(r, "</table>\n"); break;
@@ -529,6 +546,15 @@ text_callback(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, void* userdat
         case MD_TEXT_SOFTBR:    RENDER_VERBATIM(r, (r->image_nesting_level == 0 ? "\n" : " ")); break;
         case MD_TEXT_HTML:      render_verbatim(r, text, size); break;
         case MD_TEXT_ENTITY:    render_entity(r, text, size, render_html_escaped); break;
+        case MD_TEXT_CODE:     {
+                                    if (r->code_hilite && r->code_hilite->in_code_block) {
+                                        r->code_hilite->text(type, text, size, r->code_hilite->userdata);
+                                    }
+                                    else {
+                                        render_html_escaped(r, text, size);
+                                    }
+                                    break;
+                                }
         default:                render_html_escaped(r, text, size); break;
     }
 
@@ -547,9 +573,9 @@ int
 md_html(const MD_CHAR* input, MD_SIZE input_size,
         void (*process_output)(const MD_CHAR*, MD_SIZE, void*),
         void* userdata, unsigned parser_flags, unsigned renderer_flags,
-        MD_TOC_OPTIONS* toc_options)
+        MD_TOC_OPTIONS* toc_options, MD_HTML_CODE_HILITE* code_hilite)
 {
-    MD_HTML render = { process_output, userdata, renderer_flags, 0, { 0 } };
+    MD_HTML render = { process_output, userdata, renderer_flags, 0, code_hilite, { 0 } };
     int i;
 
     MD_PARSER parser = {

+ 10 - 1
markdown.mod/md4c/src/md4c-html.h

@@ -40,6 +40,15 @@
 #define MD_HTML_FLAG_XHTML                  0x0008
 
 
+typedef struct MD_HTML_CODE_HILITE MD_HTML_CODE_HILITE;
+struct MD_HTML_CODE_HILITE {
+    void* userdata;
+    int in_code_block;
+    int (*enter_block)(MD_BLOCKTYPE /*type*/, void* /*detail*/, void* /*userdata*/);
+    int (*leave_block)(MD_BLOCKTYPE /*type*/, void* /*detail*/, void* /*userdata*/);
+    int (*text)(MD_TEXTTYPE /*type*/, const MD_CHAR* /*text*/, MD_SIZE /*size*/, void* /*userdata*/);
+};
+
 /* Render Markdown into HTML.
  *
  * Note only contents of <body> tag is generated. Caller must generate
@@ -60,7 +69,7 @@
 int md_html(const MD_CHAR* input, MD_SIZE input_size,
             void (*process_output)(const MD_CHAR*, MD_SIZE, void*),
             void* userdata, unsigned parser_flags, unsigned renderer_flags,
-            MD_TOC_OPTIONS* toc_options 
+            MD_TOC_OPTIONS* toc_options, MD_HTML_CODE_HILITE* code_hilite
             );