Blog der Heimetli Software AG

MarkdownProcessor mit HTML-Template

Dieser Post dokumentiert eine Weiterentwicklung des Markdown-Prozessors. Er wurde durch eine Template-Engine erweitert, um komplette HTML-Seiten zu erstellen.

Was ist neu?

Die erste Version des MarkdownProcessors konnte aus Markdown den Inhalt einer HTML-Seite erstellen. Für eine fertige Seite braucht es aber noch einen Header und zumindest einen kleinen Footer.

Als erstes habe ich versucht, diese beiden Teile einfach zusammenzukopieren, aber das ist unflexibel, und es gibt überraschend viele Komplikationen mit dem Quoting auf der Windows Shell.

Da der Markdown-Prozessor ja schon in C# geschrieben war, bot es sich an, die ganze Arbeit im gleichen Programm zu erledigen. Das brachte sofort brauchbare Resultate.

Um die Applikation flexibel zu halten, habe ich den Aufbau der Seite in ein Template ausgelagert. Damit das wiederverwendbar ist, habe ich die Argumente von der Kommandozeile gleich als Variablen in den Code übernommen.

Das Template

Es gibt nur ein Template und das hat einen festen Namen: MarkdownProcessor.template.

Ein einfaches Template sieht zum Beispiel so aus:

<!DOCTYPE html>
<html lang="en">
 <head>
  <meta charset="utf-8">
  <meta name="description" content="$2">
  <title>$1</title>
 </head>
 <body>
$0
 </body>
</html>

Die Variablen werden mit einem $ markiert, wie das unter Linux üblich ist. $0 ist speziell, denn an dieser Stelle wird das HTML aus dem Markdown eingesetzt.

Die anderen Variablen werden von der Kommandozeile übernommen wobei die Zahl die Position des Arguments auf der Zeile angibt.

Das Markdown

Für die folgenden Beispiele wurde dieses Markdown-File benutzt:

Title
=====
Subtitle
--------

Das ist ein Paragraph
mit zwei Zeilen

Dieser Text ist **Bold**,
und dieser ist *Italic*

Die nächste Zeile erzeugt einen Horizontal Ruler:

- - -

Und wieder ein Paragraph, diesmal mit einem Link [Heimetli Software](http://www.heimetli.ch)
und einem Bild ![Logo](http://www.heimetli.ch/small.png)

Beispiel

Diese Kommandozeile

>> MarkdownProcessor in.md "Das ist der Titel" "Das ist die Description"

generiert folgende HTML-Seite:

<!DOCTYPE html>
<html lang="en">
 <head>
  <meta charset="utf-8">
  <meta name="description" content="Das ist die Description">
  <title>Das ist der Titel</title>
 </head>
 <body>
<h1>Title</h1>

<h2>Subtitle</h2>

<p>Das ist ein Paragraph
mit zwei Zeilen</p>

<p>Dieser Text ist <strong>Bold</strong>,
und dieser ist <em>Italic</em></p>

<p>Die nächste Zeile erzeugt einen Horizontal Ruler:</p>

<hr>

<p>Und wieder ein Paragraph, diesmal mit einem Link <a href="http://www.heimetli.ch">Heimetli Software</a>
und einem Bild <img src="http://www.heimetli.ch/small.png" alt="Logo"></p>

 </body>
</html>

Ja, die Leerzeilen sind effektiv drin, denn die kommen vom Markdown-Prozessor. Dem Browser ist das natürlich egal, aber mich hat es gestört...

Schöneres HTML dank Tidy

Früher war HTML Tidy ein sehr beliebtes Tool, aber es hat sehr lange gedauert, bis es endlich HTML5 akzeptiert hat.

Jetzt ist es aber verfügbar und wird anscheinend eifrig weiterentwickelt. Deshalb habe ich es auch eingesetzt:

>> MarkdownProcessor in.md "Das ist der Titel" "Das ist die Description" | tidy -config tidy.config
<!DOCTYPE html>
<html lang="en">
 <head>
  <meta charset="utf-8">
  <meta name="description" content="Das ist die Description">
  <title>
   Das ist der Titel
  </title>
 </head>
 <body>
  <h1>
   Title
  </h1>
  <h2>
   Subtitle
  </h2>
  <p>
   Das ist ein Paragraph mit zwei Zeilen
  </p>
  <p>
   Dieser Text ist <strong>Bold</strong>, und dieser ist <em>Italic</em>
  </p>
  <p>
   Die nächste Zeile erzeugt einen Horizontal Ruler:
  </p>
  <hr>
  <p>
   Und wieder ein Paragraph, diesmal mit einem Link <a href="http://www.heimetli.ch">Heimetli Software</a>
   und einem Bild <img src="http://www.heimetli.ch/small.png" alt="Logo">
  </p>
 </body>
</html>

Die Source des Programms

/******************************************************************************/
/*                                                                            */
/*                                                           FILE: Program.cs */
/*                                                                            */
/*     A Simple wrapper around MarkdownSharp                                  */
/*     =====================================                                  */
/*                                                                            */
/*     V1.00 04-MAR-2016 Te                                                   */
/*                                                                            */
/******************************************************************************/

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using MarkdownSharp;

namespace MarkdownProcessor
{
    class Program
    {
        /// <summary>
        /// This program creates a HTML page from a markdown file and
        /// a template. The template is a HTML file with variable references
        /// like this: $1.
        /// 
        /// $0 is a special case. it marks the point where the converted
        /// makrkdown text is inserted.
        /// 
        /// $1 to $9 are the command-line arguments of this program.
        /// 
        /// $$ is another special case. It prints a single $.
        /// 
        /// The name of the template file is hardcoded: "MarkdownProcessor.template".
        /// 
        /// A more detailed description and an example can be found at
        /// http://blog.heimetli.ch/markdown-processor-with-template-page.html
        /// </summary>
        static void Main(string[] args)
        {
            if( args.Length < 1 )
            {
                System.Console.WriteLine( "usage: MarkdownProcessor inputfile [arguments]" ) ;
                return ;
            }

            // Save the encoding of the console to restore it later
            Encoding encoding = System.Console.OutputEncoding ;

            Markdown markdown = new Markdown( true ) ;

            try
            {
                // Set the output encoding to UTF8
                Console.OutputEncoding = Encoding.UTF8 ;

                // Transform markdown to html
                String input   = File.ReadAllText( args[0], Encoding.Default ) ;
                String content = markdown.Transform( input ) ;

                // Read the template and replace the variables
                using( StreamReader reader = new StreamReader("MarkdownProcessor.template") )
                {
                    int    index ;
                    int    start ;
                    String line ;
                    int    pos ;
                    char   ch ;

                    while( (line = reader.ReadLine()) != null )
                    {
                        if( (pos = line.IndexOf('$')) >= 0 )
                        {
                            start = 0 ;

                            while( pos >= 0 )
                            {
                                if( pos + 1 >= line.Length )
                                    break ;

                                Console.Write( line.Substring(start,pos-start) ) ;

                                ch = line[pos+1] ;

                                switch( ch )
                                {
                                 case '0' : Console.Write( content ) ;
                                            break ;
                                 case '1' :
                                 case '2' :
                                 case '3' :
                                 case '4' :
                                 case '5' :
                                 case '6' :
                                 case '7' :
                                 case '8' :
                                 case '9' : index = ch - '0' ;
                                            if( index < args.Length )
                                                Console.Write( args[index] ) ;
                                            break ;
                                 case '$' : Console.Write( '$' ) ;
                                            break ;
                                 default  : Console.Write( '$' ) ;
                                            Console.Write( ch ) ;
                                            break ;
                                }

                                start = pos + 2 ;

                                if( start >= line.Length )
                                    break ;

                                pos = line.IndexOf( '$', start ) ;
                            }

                            if( start < line.Length )
                                Console.WriteLine( line.Substring(start,line.Length-start) ) ;
                            else
                                Console.WriteLine() ;
                        }
                        else
                        {
                            Console.WriteLine( line ) ;
                        }
                    }
                }
            }
            catch( Exception e )
            {
                 Console.Error.WriteLine( "Exception: " + e.Message ) ;
            }

            // Restore the output encoding
            Console.OutputEncoding = encoding ;
        }
    }
}

Es hat ziemlich lange gedauert, bis das Encoding überall korrekt war. Das Programm liest ein Windows-File und gibt eine HTML-Seite in UTF-8 aus.

Downloads

Was, Sie sind immer noch am Lesen?

Dann sind Sie aber ernsthaft interessiert und bekommen zur Belohnung ein paar Downloads. Hier ist ein ZIP mit MarkdownProcessor.exe, MarkdownProcessor.template und md.in.

Aber auch die Source brauchen Sie nicht abzutippen: Program.cs