0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

MarkdownからHTMLを生成しよう

Posted at

なぜ

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

最終方針

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

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

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をコピーするだけです。
さて、バッチファイルは以下の通りです。

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 行ずつ読み込む (完全版?)

ちなみに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 をクライアント側でパースして表示する

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

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エラーが発生したので解決した

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

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

ここで記事を終えるのも何なので、スタイルを整えたベースの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/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;
}
0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?