MarkdownからHTMLを生成しよう

## なぜ
作ったツールをリリースする際に、必要なもの、それは使い方の説明書です。
最近は、Readme.mdを作成して、HTML化することが流行っていますね。
ただ、状況によっては、GitHubにソースをPushできなかったり、Readme.mdをHTML化する技術を持たない人にリリースしなくてはならないケースもあります。
そんなときに自動的にHTMLを作ってしまおうということで本記事を作成しました。

## 最終方針
前提として、受け取る人は、デフォルトのWindowsしか使えない一般人とします。
自力で、MarkdownをHTML化できる人なんかは、勝手にやってもらえばよいのです。

最終的に、ベースとなるhtmlの特定個所に別ファイルに記述したMarkdownをbatch処理でコピーする方針となりました。ベースのhtmlは、marked.jsのサンプルを一部改変して、以下としました。
[marked](https://github.com/markedjs/marked)

```html:base.html
<!doctype html>
<html>
<head>
  <meta charset="utf-8"/>
  <title>Marked in the browser</title>
</head>
<body>
  <div id="content"></div>
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
  <script>
    document.getElementById('content').innerHTML =
      marked(' \
$include
');
  </script>
</body>
</html>
```

$includeに別ファイルに記述したReadme.mdをコピーするだけです。
さて、バッチファイルは以下の通りです。

```batch:generate-markdown.bat
@echo off

chcp 65001

set OutputFile="Readme.html"

echo. > %OutputFile%

for /f "tokens=1* delims=: eol=" %%a in ('findstr /n "^" base.html') do (
  if "%%b"=="$include" (
    for /f "tokens=1* delims=: eol=" %%x in ('findstr /n "^" Readme.md') do (
      (echo.%%y\n\) >> %OutputFile%
    )
  ) else (
    (echo.%%Y) >> %OutputFile%
  )
)
```
なんじゃ、こりゃ。。。
ただ、文字列を置き換えるだけなのに大層な呪文になりました。
動作概略は以下の通りです。
- 入出力をUTF-8に指定
- 空のReadme.htmlを作成
- base.htmlを1行ずつ読み込み、読み込んだ行をReadme.htmlに追記
- $includeを検出するとReadme.mdを一行ずつ読み込み、改行前に\nと\を追加してReadme.htmlに追記

batchでファイルを読み込む際は、スペース区切りになってしまったり、空行が無視されたりするので、上記のような呪文になってしまいます。
詳細は、[バッチファイル | テキストファイルを 1 行ずつ読み込む (完全版？)](https://archive.kerupani129.net/blog/posts/%E3%83%90%E3%83%83%E3%83%81%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB-%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92-1-%E8%A1%8C%E3%81%9A%E3%81%A4%E8%AA%AD%E3%81%BF/)

ちなみにbashで書くと以下のようになります。

```bash:generate-markdown.sh
#!/bin/bash

IFS=$'\n'
OFILE="Readme.html"

cp /dev/null $OFILE

for line in `cat base.html`
do 
  if [[ $line == *\$include* ]]; then
    perl -pe 's/\r\n|\r|\n/\\n\\\n/g' Readme.md >> $OFILE
  else
    echo $line >> $OFILE
  fi
done
```
perlの正規表現置換を心置きなく使えるので、すっきりですね。

## なぜ、こうなった！？
そもそも、jQueryでローカルファイルを読み込んでくればいいんでない？と思う方がいるかもしれません。
ええ、筆者もその一人でした。とても、よい記事があります。
[Marked.js で Markdown をクライアント側でパースして表示する](https://qiita.com/amay077/items/704d48130e5cf17e8654)

コピペして使ってみましょう！

```
This content failed to load.
```

あれ、パス指定を間違えたかな？
ctrl+shift+iを押して、、、

```
Access to XMLHttpRequest at 'file:///C:/Users/qiita/Documents/Markdown/Readme.md' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, chrome-untrusted, https.
```
なんじゃこりゃ...

どうも、最新(2021/3時点)のブラウザでは、ローカルファイルをハードコーディングして指定してもアクセスできないようです。
[【Ajax】ローカルファイルを読み込もうとしたらCORSエラーが発生したので解決した](https://qiita.com/terufumi1122/items/39b2a3659bc585c07f64)

ここに書いてある解決方法を一般のWindowsユーザーに試してもらうことなんて、当たり前ですが、できません。
また、ホストを別に準備できない場合もあります。

なので、1手間の面倒ですが、バッチファイルを実行してもらう形式にしたわけです。

ここで記事を終えるのも何なので、スタイルを整えたベースのhtmlを参考までに記載しておきます。

```html:base.html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8"/>
    <title>
      Readme
    </title>
  </head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/2.0.1/marked.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.2/highlight.min.js"</script>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.2/styles/default.min.css">
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
  <link href="css/mystyle.css" rel="stylesheet">
  <body>
    <div class="container">
      <div class="main">
        <div class="header-content">
          <div id="content"> </div>
        </div>
      </div>
      <div class="side">
        <div id="headline-preview"> </div>
      </div>
    </div>
  </body>
<script>
hljs.highlightAll();

function headline(markdown){
    lines = markdown.split(/\r\n|\r|\n/);
    var headline = {};
    var in_code = false;
    for(var it in lines){
        var line = lines[it]
        var top_c = line[0]
        if(lines[it].match(/^```.?/)){
            in_code = in_code ? false : true;
        }
        if(lines[it][0] == '#' && !in_code){
            level = ( lines[it].match( /#/g, ) || [] ).length;
            str = lines[it].replace(/^#+/, '').trim()
            headline[str] = level;
        }
    }

    var preview = "<ul>";
    var cur = 1;
    for(var key in headline){
        var ldif = headline[key] - cur;
        if (ldif > 0){
          for(var i = 0; i < ldif; i++){
            preview += "<ul>";
          }
        }
        else if(ldif < 0){
          for(var i = 0; i < -ldif; i++){
            preview += "</ul>";
          }
        }
        cur = headline[key];
        name = key;
        key = key.toLowerCase();
        key = key.replace(' ', '-');
        preview += "<li>" 
                + '<a href="#'
                +  key
                + '">'
                + name
                + "</a></li>";
    }
    preview += "</ul>";
    document.getElementById('headline-preview').innerHTML = preview;
}

var markdown = '\
$include
';

let renderer = new marked.Renderer()
renderer.table = function(header, body) {
    if (body) body = '<tbody>' + body + '</tbody>';

    return '<table class="table table-striped">\n'
      + '<thead>\n'
      + header
      + '</thead>\n'
      + body
      + '</table>\n';
  }

mhtml= marked(markdown, {renderer:renderer});
document.getElementById('content').innerHTML = mhtml;
headline(markdown);
</script>
</html>
```

```css:css/mystyle.css
img {
    max-width: 100%;
}
.container {
  margin: auto;
  display: flex;
  justify-content: space-between;
  width: 1030px;
}
.main {
  width: 640px;
}
.side {
  position: -webkit-sticky; /* Safari */
  position: sticky;
  top: 0;
  width: 300px;
  height: 100%;
}
h1, h2, h3 {
  margin-block-start: 1rem;
  margin-block-end: 1rem;
  margin-inline-start: 0px;
  margin-inline-end: 0px;
  font-size: 1.17em;
  display: block;
  font-weight: bold;
}
.header-content h1, .header-content h2, .header-content h3,
.header-content h4, .header-content h5, .header-content h6{
  font-weight: bold;
  line-height: 1.5;
  font-feature-settings: "palt";
  margin-top: 1.5em;
  margin-bottom: 1.5em;
  cursor: pointer;
  position: relative;
}
.header-content h1{
  font-size: 1.8em;
  border-bottom: 2px solid #ddd;
  padding-bottom: 0.2em;
}
.header-content h2{
  font-size: 1.6em;
  padding-bottom: 0.1em;
}
```