LoginSignup
32
14

【MATLAB】ライブスクリプトの Markdown 変換で楽して Qiita 投稿

Last updated at Posted at 2020-12-16

バージョン R2023b からexport 関数で markdown への変換ができるようになりました!ぜひ使ってみてください。

簡単ですが使い方をこちらで紹介しました。
export 関数でライブスクリプトから markdown 変換(R2023b 新機能)

はじめに

Qiita に投稿するって正直なところ手間かかります。

  • プロットを画像にして
  • スクリプトコピペして・・

一度で終わるならともかくコード変更したのでもう一回、とかやってられませんよね。

そこで、MATLAB の実行結果をそのまま Qiita 記事にして楽をしたいと、ライブスクリプトを Qiita の Markdown に変換させる関数を作りました。ライブスクリプトが完成したら Markdown に自動変換してどんどん投稿しちゃいましょう!

attach:cat

コードはこちら GitHub: livescript2markdown

R2019b 以降の MATLAB で使えます。関数自体は結構前に公開していたんですが、ここでは実装内容に少しだけ踏み込んで紹介します。

ライブスクリプトからスライド作成に発展したケースも。
YouTube: ライブスクリプトを発表で使いたい!ライブスクリプトからスライドへの変換

インストール方法

Pavel さんが簡単にインストールできるようパッケージ化してくれています。

File Exchange: Live Script To Markdown Converter

MATLAB からですとアドオンエクスプローラーからインストールするのが一番簡単。livescript2markdown で検索して2番目に出てくるものをインストールしてください。

キャプチャ.PNG

こんな人向け

  • Qiita に投稿したいけど Markdown がなんだかめんどくさい/敷居が高い・・
  • MATLAB で作った Figure を保存して、Qiita にペーストしてという作業がめんどくさい・・
  • コードを変更するたびに Figure 保存しなおし、記事を修正してという作業がめんどくさい・・
  • ライブスクリプトは好き

結果的に GitHub の Markdown にも対応してますので README.md を書くのも楽ちん。ただ、GitHub の場合は数式がそのままでは表示できないので別のサービスを使って表示させます。

こんな人には向いてないかも

MATLAB コードを実行しない人。Qiita 上で直接書いた方が楽だと思います。多分。

使い方と注意点

使い方はこちらの README をどうぞ。

latex2markdown 関数自体は LaTeX から Markdown に変換するものなので、まず手動でライブスクリプトを LaTeX に変換する必要があります。ごめんなさい。また、残念ながらうまく変換できないケースがたまにありますので、変換後の確認は必要です。こちらもごめんなさい。

attach:cat

LaTeX へ変換する関数はありますが、非公式(仕様が変更される可能性あり)なので立場上紹介できません。そこを汲み取って livescript2markdown.m を実装してくれた方がいらっしゃいます。

こちらからどうぞ:https://github.com/roslovets/livescript2markdown

補足:Word や HTML から Markdown にしてもよかったのですが、LaTeX が一番簡単でした。

画像やプロットはどうする?

ライブスクリプトから LaTeX に出力した時点で関連画像(ライブスクリプト内で使用した画像や Figure) は (filename)_images というフォルダに保存されます。その画像を引用する形で LaTeX の構文を Markdown に変換。

Qiita の場合はその画像ファイルを Drag & Drop!

実装の解説

基本的には必要のない部分を削除 + 正規表現を使って LaTeX の構文を Markdown に置き換える作業を地道にやっていくだけです。string 型変数を多用します。正規表現のいい練習になりました。

まずはエンコーディング問題

ライブスクリプトから LaTeX に出力して R2019b 以前の MATLAB で開くと・・


\begin{par}
\begin{flushleft}
繝ゥ繧、繝悶せ繧ッ繝ェ繝励ヨ}�シ亥、夜Κ繝上う繝代�シ繝ェ繝ウ繧ッ縺ョ萓具シ峨r
\end{flushleft}
\end{par}

日本語が文字化けします。

出力される .tex ファイルのエンコーディングは UTF-8 なので R2019b 以前の MATLAB だとこれをそのまま MATLAB で開くと日本語文字が文字化けしてしまいます・・ですので、ここは慌てず fgets をつかって読み込みます。

Code(Display)
% latex を読み込み string 型に変更
fid = fopen(latexfile,'r','n','UTF-8');
str = string;
tmp = fgets(fid);
while tmp > 0
    str = append(str,string(tmp));
    tmp = fgets(fid);
end
fclose(fid);

R2020a 以降だと

Code(Display)
str = string(fileread(latexfile));

だけで OK です。後は正規表現を使って Markdown に変更していきます。

変更したフォーマット

以下の処理方法について紹介します。

  1. 文字装飾
  2. 引用パラグラフ
  3. 不要なコマンド群
  4. 見出しの変更
  5. MATLAB コード
  6. 実行しないコード例
  7. 出力表示
  8. テーブルの出力表示
  9. ハイパーリンク
  10. リスト
  11. リスト(数付き)
  12. 画像
  13. 数式
  14. シンボリック出力

思った以上に多い・・ですが、LaTeX の柔軟なシンタックスに比べるとかなり限定的。1つ1つ地道に対応していきます。

興味ある人は開いてみてね

1. 文字装飾

ライブスクリプトで対応しているのは、太字イタリック、下付き文字、等幅文字とそれぞれの組み合わせです。

\textbf{太字}\textit{イタリック}\underline{下線付き文字}\texttt{等幅文字}

それぞれ Markdown だと

**太字**、*イタリック*、`等幅文字`

ですね。

下線付き文字については Markdown に対応するものがありません。またそれぞれの組み合わせも考える必要があるので、変換すべきは構文は以下の通り。


\textit{\textbf{イタリック太字}}\texttt{\textbf{等幅太字}}\underline{\textbf{下線付き太字}}\underline{\textit{\textbf{下線付きイタリック太字}}}\texttt{\underline{\textit{\textbf{下線付きイタリック等幅太字}}}}
Code
tmp = "\texttt{\underline{\textit{\textbf{下線付きイタリック等幅太字}}}}";

を例にやってみます。

Code
tmp = regexprep(tmp,"\\textbf{([^{}]+)}","**$1**");
tmp = regexprep(tmp,"\\textit{([^{}]+)}","*$1*");
tmp = regexprep(tmp,"\\underline{([^{}]+)}","$1");
final = regexprep(tmp,"\\texttt{(\*{0,3})([^*{}]+)(\*{0,3})}","$1`$2`$3")
Output
final = "***`下線付きイタリック等幅太字`***"

こんな感じで正規表現を使ってどんどん変更していきます。

  • ^{} は、{ } 以外の任意の文字 }
  • +[]で囲んだ文字のの1 回以上の繰り返し
  • () はトークン化

を意味するのでそれぞれのコマンド (例えば \\textbf) の中に入った任意の文字列をトークンとして取り出す処理を意味しています。

ちなみに最後の \\texttt だけややこしいことをしているのは、` で * を囲んでしまうとイタリック表示にならないからです。

Code
regexprep(tmp,"\\texttt{([^{}]+)}","`$1`")
Output
ans = "`***下線付きイタリック等幅太字***`"

** が 最も内側に来るようにする必要があります。

詳細:processDocumentOutput.m

2. 引用パラグラフ

これは LaTeX だと


\begin{par}
\begin{center}
 xxxx
\end{center}
\end{par}

Markdown だと文の頭に > を付けるだけですよね。ここは文字列の入れ替え replace 関数で対処します。

Code(Display)
tmp = replace(tmp,"\begin{par}"+newline+"\begin{center}"+newline,"> ")

詳細:processDocumentOutput.m

3. 不要なコマンド群

目次や \begin{flushleft} など文字の位置を指定するコマンドはこの時点で削除しちゃいます。erase 関数が便利。

Code(Display)
tmp = erase(tmp,"\begin{par}"+newline);
tmp = erase(tmp,"\end{par}"+newline);

newline (改行) も合わせて削除しておかないと改行だらけの Markdown が出てきます。別に困りませんけど。

詳細:processDocumentOutput.m

4. 見出しの変更

ライブスクリプトから出力した LaTeX では MATLAB 側で定義したスタイルシートに基づいた構文になっている点に注意が必要。見出しが一番分かりやすい例です。ライブスクリプトで対応しているのは以下の4つの見出し。


\matlabtitle
\matlabheading
\matlabheadingtwo
\matlabheadingthree

タイトルは Markdown にはありませんが、その他はそれぞれ #, ##, ### ですよね。先ほどと同じで、正規表現を使って置き換えると、

Code(Display)
tmp = regexprep(tmp,"\\matlabtitle{([^{}]+)}","Title: $1");
tmp = regexprep(tmp,"\\matlabheading{([^{}]+)}","# $1");
tmp = regexprep(tmp,"\\matlabheadingtwo{([^{}]+)}","## $1");
tmp = regexprep(tmp,"\\matlabheadingthree{([^{}]+)}","### $1");

こんなコードになります。

詳細:processDocumentOutput.m

5. MATLAB コード

ライブスクリプトから出力した LaTeX では


\begin{matlabcode}
(コード)
\end{matlabcode}

Markdown では以下の構文

```matlab
(コード)
```

ですので、

Code(Display)
tmp = replace(tmp,"\begin{matlabcode}","```matlab");
tmp = replace(tmp,"\end{matlabcode}","```");

でOK。

詳細:processLiteralOutput.m

6. 実行しないコード例

実行されない見せているだけのコードも同様に処理します。ライブスクリプトでは「MATLAB」と「標準」の2種類用意されてあります。

MATLAB例

Code(Display)
% MATLAB のロゴに似たプロット
surf(peaks)

標準例

Code(Display)
% MATLAB のロゴに似たプロット
surf(peaks)

「MATLAB」コードの色付けが行われるという違いがありますが、LaTeX では \begin{verbatim}\begin{lstlisting} として登場して、どちらも同じ見栄えになります。上と同じく以下の構文

```matlab
(コード)
```

で置き換えます。

詳細:processLiteralOutput.m

7. 出力表示

MATLAB の出力値は LaTeX では \begin{matlabtoutput} が使われています。table 型など、データ型によってはややこしいことになりそうですが、ここでは単純に

Code(Display)
tmp = replace(tmp,"\begin{matlaboutput}","```");
tmp = replace(tmp,"\end{matlaboutput}","```");

で処理します。

詳細:processLiteralOutput.m

8. テーブルの出力表示

これは Markdown で直書き使用すると涙が出るパート。処理に少し手間かかりましたが必須項目です。

LaTeX だと


\begin{matlabtableoutput}
{
\begin{tabular} {|l|c|r|}\hline
\mlcell{TD} & \mlcell{TD} & \mlcell{TD} \\ \hline
\mlcell{TD} & \mlcell{TD} & \mlcell{TD} \\
\hline
\end{tabular}
}
\end{matlabtableoutput}

Markdown では

| TH left | TH center | TH right |
| :--- | :---: | ---: |
| TD | TD | TD |
| TD | TD | TD |

ということで、

Code(Display)
tableLatex = extractBetween(str2md(idxTblOutput),"\begin{tabular}","\end{tabular}");
tablecontents = split(tableLatex,"\hline");
formatLatex = tablecontents(1); % {|l|c|r|}
headerLatex = tablecontents(2); % \mlcell{TD} & \mlcell{TD} & \mlcell{TD} \\ \hline
bodyLatex = tablecontents(3:end); % and the rest.
    
format = regexp(formatLatex,"\{([^{}]+)}",'tokens');
format = format{:};
format = replace(format, "c",":--:");
format = replace(format, "l",":--");
format = replace(format, "r","--:");

という感じで、まず \hline で行ごとに分割して、フォーマット部分(formatLatex)、ヘッダー部分(headerLatex)、ボディ部分(bodyLatex)に分けて変換しています。

詳細:processDocumentOutput.m

9. ハイパーリンク

Markdown では[テキスト](http://xxx.com)、LaTeX では \href{http://xxx.com}{テキスト} ですね。これ多少ややこしい、かつ、すべてのケースを網羅していない可能性が十分にあるのですが・・

Code(Display)
tmp = regexprep(tmp,"\\href{([^{}]+)}{(\w+)}","[$2]($1)");

でどうでしょう。

詳細:processDocumentOutput.m

10. リスト

Markdown では各項目に - 付けるだけですが、LaTeX では(\setlength は現時点で削除)


\begin{itemize}
\setlength{\itemsep}{-1ex}
   \item{\begin{flushleft} リスト1 \end{flushleft}}
   \item{\begin{flushleft} リスト2 \end{flushleft}}
   \item{\begin{flushleft} リスト3 \end{flushleft}}
\end{itemize}

もちろん文章中にいくつものリスト構文があることを想定して

Code(Display)
parts = extractBetween(tmp,"\begin{itemize}","\end{itemize}",'Boundaries','inclusive');
partsQiita = regexprep(parts,"\\item{([^{}]+)}","- $1");
for ii=1:length(parts) % それぞれのリスト毎に変換(置換)
    tmp = replace(tmp,parts(ii),partsQiita(ii));
    tmp = erase(tmp,"\begin{itemize}"+newline);
    tmp = erase(tmp,"\end{itemize}"+newline);
end

なぜ extractBetween 関数の実行の時に 'Boundaries','inclusive' としているかというと、次の数付きリストと区別するためです。どちらも \item を使っちゃっているので。

詳細:processDocumentOutput.m

11. リスト(数付き)

Markdown では 1. item name という構文。LaTeX では以下(\setlength は現時点で削除)


\begin{enumerate}
    \setlength{\itemsep}{-1ex}
    \item{\begin{flushleft} リスト1 \end{flushleft}}
    \item{\begin{flushleft} リスト2 \end{flushleft}}
    \item{\begin{flushleft} リスト3 \end{flushleft}}
\end{enumerate}

こちらは

Code(Display)
parts = extractBetween(tmp,"\begin{enumerate}","\end{enumerate}",'Boundaries','inclusive');
partsQiita = regexprep(parts,"\\item{([^{}]+)}","1. $1"); % 先頭の数字は何でもOK
for ii=1:length(parts) % それぞれのリスト毎に変換(置換)
    tmp = replace(tmp,parts(ii),partsQiita(ii));
    tmp = erase(tmp,"\begin{enumerate}"+newline);
    tmp = erase(tmp,"\end{enumerate}"+newline);
end

こんな具合。先頭に付ける数値は何でも良いので変更しやすいです。

詳細:processDocumentOutput.m

12. 画像

冒頭でも触れましたが ライブスクリプトから LaTeX を生成した時点で Figure 等は画像として (filename)_images というフォルダに保存されているはず。

Markdown だと

![代替テキスト](http://image.jpg)

こんな構文ですよね。Qiita の場合はいずれにしても画像ファイルを該当箇所に drag & drop する必要があるのでファイル名さえわかればよいということで、上の構文にしておきます。

ちなみに LaTeX では

\includegraphics[width=\maxwidth{56.196688409433015em}]{ファイル名}

です。

また ライブスクリプト内に貼り付け画像の場合、拡張子が省略されるケースがあるので要注意。画像フォルダ内を検索して実際のファイル名を挿入します。

Code(Display)
parts = regexp(tmp,"\\includegraphics\[[^\]]+\]{([^{}]+)}", "tokens");
for ii=1:length(parts)
    imagefilename = ls(imagedir + parts{ii} + "*");
    tmp = regexprep(tmp,"\\includegraphics\[[^\]]+\]{"+parts(ii)+"}",...
        "!["+imagefilename+"]("+imagedir+imagefilename+")");
end

詳細:processincludegraphics.m

13. 数式

文中の数式は LaTeX で $equation$ なのでそのまま Markdown でも通用しますが、$$equation$$ とされている部分は、

```math
(equation)
```

に変更します。

Code(Display)
tmp = regexprep(tmp,"\$\$([^$]+)\$\$","```math" + newline + "$1" + newline + "```");

Qiita の場合はこれでブラウザから見たときに数式をレンダリングしてくれるのですが、GitHub の場合は残念ながらレンダリングしてくれません。

GitHub 用の Markdown を作成する際には「GithubのREADMEとかwikiで数式を書く」を参考に https://latex.codecogs.com のサービスを利用します。詳細はコードの方見てみてください。

詳細:processEquations.m

14. シンボリック出力

例えばこういう出力。

Code
syms x y
int(sin(x),x)

ans =

 -\cos\left(x\right)

LaTeX では


\begin{matlabsymbolicoutput}
ans =
     $\displaystyle -\cos \left(x\right)$
\end{matlabsymbolicoutput}

こんな出力になります。他にも


\begin{matlabsymbolicoutput}
a = 
    $\displaystyle \left(\begin{array}{cccc}
\cos \left(\theta \right) & -\sin \left(\theta \right) & 0 & 0\\
\sin \left(\theta \right) & \cos \left(\theta \right) & 0 & 0\\
0 & 0 & 1 & 0\\
0 & 0 & 0 & 1
\end{array}\right)$
\end{matlabsymbolicoutput}

こんな行列表示の出力もあります。ここは数式部分を $$ で囲んで、上の通常の数式と同時に処理するようにしています。

Code(Display)
symoutIdx = contains(str2md,["\begin{matlabsymbolicoutput}","\end{matlabsymbolicoutput}"]);
symoutParts = str2md(symoutIdx);
tmp = erase(symoutParts,"\begin{matlabsymbolicoutput}"+newline);
tmp = replace(tmp,"$\displaystyle","$$");
partsMarkdown = replace(tmp,"$"+newline+"\end{matlabsymbolicoutput}","$$");
str2md(symoutIdx) = partsMarkdown;

詳細:processDocumentOutput.m

まとめ

他にも細かいものがちょこちょこありますがこれでひとまず完成。ファイルに出力して終了です。

Code(Display)
mdfile = filename + ".md";
fileID = fopen(mdfile,'w');
fprintf(fileID,'%s\n',tmp);
fclose(fileID);

地味な作業でした。

もちろん、この Qiita 記事の Markdown も latex2markdown.m で作られたものです。

32
14
1

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
32
14