無粋な日々に

頭の中のメモ。分からないことを整理する

はてなブログで日々数式を含んだ記事を書く

はてなブログを復活して2週間。はてなブログTeXを使って数式を書くのが大変で当初ずっとイライラしていた。現時点での私の対応策を書きたいと思う。

まずQiitaと比べて圧倒的に面倒な点は次:

  1. 下書きがリアルタイムでレンダリングできない(数式、リンクを含む一部)
  2. TeXの記述が独特で特にエスケープがめちゃくちゃ面倒

1の対策. Typoraで下書き

[1]に関しては、Typora — a markdown editor, markdown reader.を使うことで、markdown + TeX をリアルタイムにて下書きを確認している。私はTeXもそんなに慣れているわけではないのでリアルタイムにレンダリングしてくれないと苦しい

Typoraで下書きをすれば、TeXもリアルタイムに確認できる

f:id:messefor:20200802190337p:plain
ypora

TeXはmathpixで

ちなみにTeXコードも複雑な式はMathpix Snipを使って、手書きノートの写真から直接TeXコードに変換している。

f:id:messefor:20200802191311p:plain
mathpix

たとえばこれが、

f:id:messefor:20200802194010p:plain
formula

mathpix で読み込むとこうなる。 f:id:messefor:20200802194307p:plain

もちろん完璧ではないが、精度も十分。めちゃ便利。なおこれはpngで出力したもの。

2の対策. スクリプトで置換

当初、はてなブログでのTeXエスケープの方法を探り探りやっていたが、結局以下の記事に記載してあることが全てだった。

ano3.hatenablog.com

上記ブログに設置してあるフォームから変換できるのだが、作業の流れ上Typoraで書いた.mdファイルをそのまま一気に変換したかったので、とりいそぎ自前でPythonスクリプトを書いた。

実行部分

path_art = 'article.md'
path_art_cnvd = 'article_new.md'

# 変換対象のテキストファイルの読み込み
with open(path_art, 'r') as f:
    artile_strings = f.read()

# 変換 
rht = ConvertHatenaTex()
artile_strings_new = rht.replace(artile_strings)

# 変換後のファイルを保存
with open('article_new.md', 'w') as f:
    f.write(artile_strings_new)

やっていることは、$$~$$$~$で囲まれたブロックを見つけてきて、愚直に必要部分を置換しているだけ。

全体

"""Convertace article with TEX syntax with Hatena Blog Syntax """
import os
import re
from typing import List, Callable
from copy import deepcopy


def replace_m(x: str, rep_map: List[dict]) -> str:
    for pattern, repl in rep_map:
        x = x.replace(pattern, repl)
    return x

def add_tags(x: str, tag_pre: str, tag_post: str) -> str:
    """Concatenate string and tags"""
    return tag_pre + x + tag_post


class ConvertHatenaTex:
    """Convert TEX syntax to Hatena Blog TEX Syntax.

    """

    def __init__(self):

        self.pattern_block = r'\$\$([\S\s]*?)\$\$'
        self.pattern_inline = r'\$([a-zA-Z0-9!-/:-@¥[-`{-~ ]+)\$'

        self.rep_map_block = [('[', r'\['), (']', r'\]'),]
        self.rep_map_inline = [('[', r'\\['), (']', r'\\]'),
                                ('_', r'\_'), ('^', '^ '),
                                (r'\{', r'\\{') ,(r'\}', r'\\}')]


        tag_pre_b = '<div  align="center">\n[tex:\displaystyle{\n\n'
        tag_post_b = '\n}]\n</div>\n'

        # tag_pre_b = '<pre>\n[tex:\n\n'
        # tag_post_b = '\n]\n</pre>\n'

        self.tags_block = (tag_pre_b, tag_post_b)

        tag_pre_i = '[tex:\displaystyle{'
        tag_post_i = '}]'

        # tag_pre_i = '[tex:'
        # tag_post_i = ']'

        self.tags_inline = (tag_pre_i, tag_post_i)

    def replace(self, artile_strings: str) -> str:
        x = deepcopy(artile_strings)
        x = self._replace(x, self.pattern_block, self.proc_in_blocks)
        x = self._replace(x, self.pattern_inline, self.proc_in_lines)
        return x

    def proc_in_blocks(self, x: str) -> str:
        """Process replacing lines in blocks"""
        x = replace_m(x, self.rep_map_block)
        x = add_tags(x, *self.tags_block)
        return x

    def proc_in_lines(self, x: str) -> str:
        """Process replacing in lines"""
        x = replace_m(x, self.rep_map_inline)
        x = add_tags(x, *self.tags_inline)
        return x

    def _replace(self, artile_strings: str, pattern: str,
                            rep_func: Callable[[str], str]) -> str:
        """Extract block/inline parts and replace"""
        prog = re.compile(pattern)
        results = prog.finditer(artile_strings)

        replace_strs = []
        n_init = 0
        for mg in results:

            str_chunk = mg.group(1)

            str_chunk = rep_func(str_chunk)

            n_start, n_end = mg.span()
            str_pre = artile_strings[n_init:n_start]

            replace_strs.append(str_pre)
            replace_strs.append(str_chunk)
            n_init = n_end

        replace_strs.append(artile_strings[n_end:])
        artile_strings_new = ''.join(replace_strs)

        return artile_strings_new



if __name__ == '__main__':

    path_art = 'article.md'
    path_art_cnvd = 'article_new.md'

    # Load a article to be replaced
    with open(path_art, 'r') as f:
        artile_strings = f.read()

    rht = ConvertHatenaTex()
    artile_strings_new = rht.replace(artile_strings)

    # Save the replaced article
    with open(path_art_cnvd, 'w') as f:
        f.write(artile_strings_new)


人生残り短い中年に残された時間は少ない。日々のアウトプットにあまり時間を使いすぎるのは精神衛生上良くない。

オススメの方法があったら教えてください。