日本語の改行位置を自動的に認識してくれるBudouXというライブラリがリリースされたのを知りました。
このBlogのMarkdown Parser として使用しているPython-Markdown に処理が組み込みができると記事が読みやすくなりそうなので、実施できるか試してみました。 実施した内容を記載します。
ライブラリのインストール
Python-Markdownと、BudouXをpipでインストールします。
!pip install markdown
!pip install budoux
BudouX の動作確認
インストール後、まずBudouXの動作を確認します。
import budoux
parser = budoux.load_default_japanese_parser()
print(parser.parse('今日は晴れています。'))
print(parser.translate_html_string('今日は晴れています。'))
['今日は', '晴れています。']
<span style="word-break: keep-all; overflow-wrap: break-word;">今日は<wbr>晴れています。。</span>
テキストに対して形態素解析だけ実施する。parse
というメソッドと、テキストに対して、形態素解析し、htmlとして修飾するtranslate_html_string
というメソッドが提供されています。どちらも問題なく動作しました。
Python-Markdown の拡張ポイントについて
Python-Markdown には、extention という拡張機能を登録できます。
これは拡張記法を登録する機能で、独自記法の登録ができます。
今回実施したいのは、通常の文章に対してのBudouXの処理を差し込みとなるため、extentionは使用できなさそうでした。
Python-Markdownには、処理フェーズごとにprocessorの登録ができBudouXの処理はこのprocessorで実施するとうまくいきそうです。
処理フェーズごとにprocessorは以下のように分かれています。
- 前処理を行う preprocessor。
- ブロックレベルの処理を行い、ElementTreeにする blockprocessor。
- ElementTreeに対して処理を行う treeprocessor。
- treeprocessorから呼び出される inlineprocessor。
- 後処理を行う postprocessor。
今回拡張が必要だったのでは、blockprocessor で、ヘッダーとパラグラフの文字列にBudoXの処理での編集を行えれば良さそうでした。
BudouXHashHeaderProcessor と、BudouXParagraphProcessor を作成、デフォルトのblockprocessor ではなく作成したprocessorに置き換えるようにしました。 作成したコードは以下の通りです。
import markdown
import re
import xml.etree.ElementTree as etree
from markdown.blockprocessors import BlockProcessor, BlockParser,EmptyBlockProcessor,ListIndentProcessor,CodeBlockProcessor,HashHeaderProcessor,SetextHeaderProcessor,HRProcessor,OListProcessor,UListProcessor,BlockQuoteProcessor, ReferenceProcessor, ParagraphProcessor
# BudouXのインポート、parserの生成
import budoux
parser = budoux.load_default_japanese_parser()
import logging
logger = logging.getLogger("TEST")
# BudouX を呼びだしを行うProcessorクラス その1 (これはヘッダーに対して処理を行う)
class BudouXHashHeaderProcessor(BlockProcessor):
""" Process Hash Headers. """
# Detect a header at start of any line in block
RE = re.compile(r'(?:^|\n)(?P<level>#{1,6})(?P<header>(?:\\.|[^\\])*?)#*(?:\n|$)')
def test(self, parent, block):
return bool(self.RE.search(block))
def run(self, parent, blocks):
block = blocks.pop(0)
m = self.RE.search(block)
if m:
before = block[:m.start()] # All lines before header
after = block[m.end():] # All lines after header
if before:
# As the header was not the first line of the block and the
# lines before the header must be parsed first,
# recursively parse this lines as a block.
self.parser.parseBlocks(parent, [before])
# Create header using named groups from RE
h = etree.SubElement(parent, 'h%d' % len(m.group('level')))
h.text = m.group('header').strip()
# BudouXでテキスト加工
h.text = parser.translate_html_string(h.text)
if after:
# Insert remaining lines as first block for future parsing.
blocks.insert(0, after)
else: # pragma: no cover
# This should never happen, but just in case...
logger.warn("We've got a problem header: %r" % block)
class BudouXParagraphProcessor(BlockProcessor):
""" Process Paragraph blocks. """
def test(self, parent, block):
return True
def run(self, parent, blocks):
block = blocks.pop(0)
if block.strip():
# Not a blank block. Add to parent, otherwise throw it away.
if self.parser.state.isstate('list'):
# The parent is a tight-list.
#
# Check for any children. This will likely only happen in a
# tight-list when a header isn't followed by a blank line.
# For example:
#
# * # Header
# Line 2 of list item - not part of header.
sibling = self.lastChild(parent)
if sibling is not None:
# Insetrt after sibling.
if sibling.tail:
# BudouXでテキスト加工 文字列が存在するので改行処理の前に実施
sibling.tail = parser.translate_html_string(sibling.tail)
sibling.tail = '{}\n{}'.format(sibling.tail, block)
else:
sibling.tail = '\n%s' % block
# BudouXでテキスト加工
sibling.tail = parser.translate_html_string(sibling.tail)
else:
# Append to parent.text
if parent.text:
# BudouXでテキスト加工 文字列が存在するので改行処理の前に実施
parent.text = parser.translate_html_string(parent.text)
parent.text = '{}\n{}'.format(parent.text, block)
else:
parent.text = block.lstrip()
# BudouXでテキスト加工
parent.text = parser.translate_html_string(parent.text)
else:
# Create a regular paragraph
p = etree.SubElement(parent, 'p')
p.text = block.lstrip()
# BudouXでテキスト加工
p.text = parser.translate_html_string(p.text)
# blockprocessors の登録処理
def build_block_parser(md, **kwargs):
""" Build the default block parser used by Markdown. """
parser = BlockParser(md)
parser.blockprocessors.register(EmptyBlockProcessor(parser), 'empty', 100)
parser.blockprocessors.register(ListIndentProcessor(parser), 'indent', 90)
parser.blockprocessors.register(CodeBlockProcessor(parser), 'code', 80)
parser.blockprocessors.register(BudouXHashHeaderProcessor(parser), 'hashheader', 70)
parser.blockprocessors.register(SetextHeaderProcessor(parser), 'setextheader', 60)
parser.blockprocessors.register(HRProcessor(parser), 'hr', 50)
parser.blockprocessors.register(OListProcessor(parser), 'olist', 40)
parser.blockprocessors.register(UListProcessor(parser), 'ulist', 30)
parser.blockprocessors.register(BlockQuoteProcessor(parser), 'quote', 20)
parser.blockprocessors.register(ReferenceProcessor(parser), 'reference', 15)
parser.blockprocessors.register(BudouXParagraphProcessor(parser), 'paragraph', 10)
return parser
# Markdown > HTML変換
md = markdown.Markdown();
# デフォルトのparserを変更
md.parser = build_block_parser(md)
html = md.convert("""
------------
## 感想、思ったこと
以下調べて思ったことになります。
* New Relic、DataDog、 Mackerel が多い。個人的に観測していた肌感とは一致した。
* Cloudwatch の印象が変わった。New Relic、DataDog等 と似たようなことはできそう。
* Sentry系のツールはそれなりに利用実績がある。統合監視って訳ではないのだろうが、フロントエンド監視に特化していて、フロントエンド監視をしたいというユースケースが多いように思えた。
* Prometheus、Grafanaの組み合わせは、オンプレミス環境で使われてそう。
* Zabbixは枯れてはいるが、プラグインインストールでモダンなこともできそう。
企業利用を軸で調べたことでツールへの先入観が消えました。
機能の比較とともに、各社何故使っているのかを考えると、自社に適用していくときの判断材料になりそうに思います。
---
## 参考
以下、参考にした記事になります。
* [Googleの「AMP優遇」がまもなく終了 - GIGAZINE](https://gigazine.net/news/20210519-google-amp-no-longer-preferential-treatment/)
Django 1.x から Django 3.x へのアップデートになり、Mezzanine というか Django のプラグインでのエラーが大量に発生しました。
なかなかエラーを解消できず、2-3時間サイト停止させてしまいました。。
事前の検証はやっておいたほうが良いかなと思いました。
""")
# 標準出力
print(html)
<hr />
<h2><span style="word-break: keep-all; overflow-wrap: break-word;">感想、<wbr>思った<wbr>こと</span></h2>
<p><span style="word-break: keep-all; overflow-wrap: break-word;">以下調べて<wbr>思った<wbr>ことになります。<wbr> </span></p>
<ul>
<li><span style="word-break: keep-all; overflow-wrap: break-word;">New Relic、<wbr>DataDog、<wbr> Mackerel が<wbr>多い。<wbr>個人的に<wbr>観測していた<wbr>肌感とは<wbr>一致した。<wbr> </span></li>
<li><span style="word-break: keep-all; overflow-wrap: break-word;">Cloudwatch の<wbr>印象が<wbr>変わった。<wbr>New Relic、<wbr>DataDog等 と<wbr>似たようなことは<wbr>できそう。<wbr> </span></li>
<li><span style="word-break: keep-all; overflow-wrap: break-word;">Sentry系の<wbr>ツールは<wbr>それなりに<wbr>利用実績が<wbr>ある。<wbr>統合監視って<wbr>訳ではないのだろうが、<wbr>フロントエンド監視に<wbr>特化していて、<wbr>フロントエンド監視を<wbr>したいと<wbr>いう<wbr>ユースケースが<wbr>多いように<wbr>思えた。<wbr> </span></li>
<li><span style="word-break: keep-all; overflow-wrap: break-word;">Prometheus、<wbr>Grafanaの<wbr>組み合わせは、<wbr>オンプレミス環境で<wbr>使われてそう。<wbr> </span></li>
<li><span style="word-break: keep-all; overflow-wrap: break-word;">Zabbixは<wbr>枯れては<wbr>いるが、<wbr>プラグインインストールで<wbr>モダンな<wbr>こともできそう。<wbr> </span></li>
</ul>
<p><span style="word-break: keep-all; overflow-wrap: break-word;">企業利用を<wbr>軸で<wbr>調べた<wbr>ことで<wbr>ツールへの<wbr>先入観が<wbr>消えました。<wbr> <br />
機能の<wbr>比較とともに、<wbr>各社何故使っているのかを<wbr>考えると、<wbr>自社に<wbr>適用していく<wbr>ときの<wbr>判断材料に<wbr>なりそうに<wbr>思います。<wbr> </span></p>
<hr />
<h2><span style="word-break: keep-all; overflow-wrap: break-word;">参考</span></h2>
<p><span style="word-break: keep-all; overflow-wrap: break-word;">以下、<wbr>参考に<wbr>した<wbr>記事に<wbr>なります。<wbr> </span></p>
<ul>
<li><span style="word-break: keep-all; overflow-wrap: break-word;"><a href="https://gigazine.net/news/20210519-google-amp-no-longer-preferential-treatment/">Googleの<wbr>「AMP優遇」が<wbr>まも<wbr>なく<wbr>終了 - GIGAZINE</a> </span></li>
</ul>
<p><span style="word-break: keep-all; overflow-wrap: break-word;">Django 1.x から<wbr> Django 3.x への<wbr>アップデートに<wbr>なり、<wbr>Mezzanine と<wbr>いうか Django の<wbr>プラグインでの<wbr>エラーが<wbr>大量に<wbr>発生しました。<wbr> <br />
なかなか<wbr>エラーを<wbr>解消できず、<wbr>2-3時間<wbr>サイト停止させてしまいました。。<wbr> <br />
事前の<wbr>検証は<wbr>やっておいた<wbr>ほうが<wbr>良いかなと<wbr>思いました。<wbr> </span></p>
期待通り処理が行えていそうです。
実施にBlogに処理を組み込んで、複数のMarkdownを読み込ませて確認をしてみようと思います。
参考
記事作成時に参考にした文書へのリンクです。
- 【Python】Python-Markdownで拡張機能を追加する(その1)
- 【Python】Python-Markdownで拡張機能を追加する(その2)
- Python-Markdownのソースを読んでみる:パーサの作り方 - Qiita
以上です。
コメント