msizの日記

ソフトウェア関係の覚え書きが中心になる予定

Sphinxで日本語文章の不自然な空白をなくしたい

Sphinx拡張機能を使って日本語文章の不自然な空白をなくす

sphinx(日本のユーザ会)で、htmlなどを作成する元ネタrestructuredtextファイルを作成するとき日本語の途中で改行を入れると、htmlなどへ変換したときにその改行箇所で空白文字が入ってしまい、ブラウザで見るときにちょっと見た目が悪くなったりします。

5年くらい前に以下の記事で対応策っぽいものを書いてましたが、数年振りにSphinxを使ってみたら、当時の記事の内容をすっかり忘れていたのでメモ。
msiz.hatenablog.jp

conf.pyの編集(1)

sphinx-quickstart」を実行したりして作成されるSphinxの設定ファイル「conf.py」で、拡張機能(を実装したPythonモジュール・パッケージ)を探すpathを調整:

...

import os
import sys
sys.path.insert(0, os.path.abspath('exts'))

...

sys.pathに追加しているだけですね。この例の場合、conf.pyのあるディレクトリに「exts」ディレクトリを作成し、そこに追加のPythonファイルを置けば探し出して読み込んでくれる。

なお、「sphinx-quickstart」で作成したconf.pyだと、それっぽい記述がコメントアウトされているので、そこを編集。

拡張機能を実装

今回は「japanese_trunc_whitespace.py」というファイル名にして、「exts」に置きました。

#!/usr/bin/python

#from docutils.core import publish_doctree, publish_from_doctree
#import sys, codecs, re, optparse
from docutils.nodes import paragraph, Text
import re

def japanese_truncate_whitespace(app, doctree, docname):
   para_list = get_para_list(doctree)
   strip_spaces_between_uchars(para_list)
   strip_spaces_around_uchars_paragraph_children(para_list)

# in order no to change literals, select only 'paragraph' nodes
#
def get_para_list(dtree):
  para_list = []
  for node in dtree.traverse():
    if isinstance(node, paragraph): para_list.append(node)
  return para_list

def strip_spaces_between_uchars(para_list):
  # non-ascii [\n\r\t] non-ascii
  __RGX = re.compile(r'([^!-~])[\n\r\t]+([^!-~])')
  # modify text inside Text node
  #
  for para in para_list:
    for node in para.traverse():
      if isinstance(node, Text):
        newtext = node.astext()
        newtext = __RGX.sub(r"\1\2", newtext)
        node.parent.replace(node, Text(newtext))

def strip_spaces_around_uchars_paragraph_children(para_list):
  # non-ascii [\s]* <End-of-TEXT>
  __RGX1 = re.compile(r'([^!-~])[\s]*$')
  # <Beginning-of-TEXT> [\s]* non-ascii
  __RGX2 = re.compile(r'^[\s]*([^!-~])')
  # modify texts over 2 nodes
  # (paragraph node can have children of Inline (reference, etc) nodes)
  #
  for para in para_list:
    prev_textnode = Text("")
    for node in para.traverse():
      new_textnode = None
      if isinstance(node, Text):
        prevtext = prev_textnode.astext()
        newtext = node.astext()
        if __RGX1.search(prevtext) and __RGX2.search(newtext):
          new_prev_textnode = Text(prev_textnode.astext().rstrip())
          new_textnode = Text(newtext.lstrip())
          prev_textnode.parent.replace(prev_textnode, new_prev_textnode)
          node.parent.replace(node, new_textnode)
          new_prev_textnode.parent = prev_textnode.parent
          new_textnode.parent = node.parent
        prev_textnode = new_textnode if new_textnode else node

def setup(app):
    app.add_config_value('japanese_trunc_whitespace', True, True)
    app.connect("doctree-resolved", japanese_truncate_whitespace)

最後の「setup(app)」で、今回の拡張機能を使用するかを「conf.py」から設定できるようにしています。

conf.pyの編集(2)

conf.pyの適当な場所で、以下の記述を追加

...

extensions = [
  'japanese_trunc_whitespace',
  ... # 他にも使用する拡張機能がある場合は指定
]
japanese_trunc_whitespace = True

...


上手くいけば、「make html」とかで作成したhtmlで、改行箇所の余分な空白がなくなるはず