msizの日記

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

日本語のreStructuredTextからdocutilsでhtmlを作成した時の不自然な空白を取り除く

sphinx(日本のユーザ会)では元ネタのrestructuredtextで、日本語の途中で改行を入れると、そこに空欄の文字が入ってしまい、ブラウザで見るときにちょっと見た目が悪くなったりすることへの対応方法が、検索すると見当たります。

sphinxを介さず、docutilsでhtml作成する時に対応するスクリプトを書いてみました。
対象となるrestructuredtextは標準出力から読み取って、作成後のhtmlは標準出力に書き出します。

#!/usr/bin/python

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

def get_doc_tree(src, encoding="utf-8-sig"):
  return publish_doctree(codecs.EncodedFile(src, encoding).read())

# 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 childre 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 output_html_from_doctree(dtree, encoding="utf-8"):
  settings_overrides = { 'output_encoding': encoding }
  return publish_from_doctree(dtree, writer_name='html',
                              settings_overrides = settings_overrides)

if __name__ == '__main__':
  optparser = optparse.OptionParser()
  optparser.add_option('-f', "--from-encoding", dest="from_encoding",
                      default="utf-8-sig",)
  optparser.add_option('-t', "--to-encoding", dest="to_encoding",
                      default="utf-8",)
  options, remainder = optparser.parse_args()
  dtree = get_doc_tree(sys.stdin, options.from_encoding)
  para_list = get_para_list(dtree)
  strip_spaces_between_uchars(para_list)
  strip_spaces_around_uchars_paragraph_children(para_list)
  html_str = output_html_from_doctree(dtree, options.to_encoding)
  sys.stdout.write(html_str)

*1

A ReStructuredText Primer」の日本語訳「ReStructuredText 入門」の「元ネタファイル」で、エラーが出なくて、意図した通りに動作しているっぽいことを確認。

ちなみに「スペース無しでもマークアップとして認識できるようにしてみた」という修正は「日本語でreStructuredText」にあります(私が作った訳じゃないですが)。

*1:2014/2/11、-fで入力データのencoding、-tで出力データのencodingを指定できるようにしました