2010年03月16日

oreilly2sphinx


taken by caribb under CC BY-NC-ND

動物本と言えばオライリー。オライリーと言えば動物本。良質な洋書を翻訳した本が多く(書き下ろしもあります)、品質は折り紙付きな出版社です。オライリーの本に接する人は大きく二つに分かれると思います:

  • 本を読む人
  • 洋書を翻訳する人

このブログで紹介するスクリプトは、後者の人で、尚かつ翻訳にSphinxを使うよ!というレアな人向けです。適当に作ってみたのですが、便利そうなので公開します。テストは手元にあったThe Art of CommunityErlang Programming の2冊で行っています。どっちも良い本です。

Sphinxを使ってオライリー本の翻訳をされる方はご利用ください。

使い方

使う時はまず、xdoc2txtを使って、PDFからテキストを抽出します。その後は次のスクリプトに解析させると、章ごとにchapter_01.rstみたいなファイルが作られます。1章の前はpreface.rst。あとはそれらをまとめるindex.rstもおまけで作られます。翻訳用に特化しているので、原文は既にコメントアウト済みで出力されます。なお、出力したテキストファイルは、input.txtにリネームして、スクリプトと同じフォルダに置いてください。手抜きなのでご勘弁を。

今後直すとしたら

一番やりたいのは、セクションタイトルをきちんと出力する部分ですね。今は階層情報がすっかり抜け落ちてしまうため、全部フラットになってしまいます。だれか、目次情報(階層付き)の抽出の仕方が分かる方、情報お願いします。タイトルが分かっていたら、今は無理矢理やっている、タイトル抽出部とかをシンプルにできるだろうしー。別ファイルで、セクションタイトルの階層構造をインデントで表現したファイルを作っておいて、それを読み込むとかでもいいかも。

スクリプトのポイント

まぁ、コメントも書いてないし、テストも書いてないぐらいの適当スクリプト(でもそれでも動いてしまうPythonの恐ろしさよ)ですが、工夫したのは@handleデコレータを作って、イベント駆動型にしたことですね。@handleを増やせば勝手に色々なフォーマットを追加できるだろうし。段落の先頭の行の抽出とか、タイトルの抽出は適当に行っているので、精度はいまいちかも。上記の二つの本を流した感じではうまくいっているようには見えましたけど。

import re

current_doc = file("preface.rst", "w")
basenames = ["preface"]

patterns = []
is_first = False
stop_words = set(
("a,about,am,among,an,and,any,are,as,at,be,by,else,"
 "ever,every,for,from,in,into,is,just,let,no,nor,not,of,"
 "off,on,only,or,own,rather,since,so,some,than,the,"
 "then,tis,to,too,twas,was,were,while,will,with,yet".split(",")))


def handle(pattern):
    def _register(func):
        if callable(pattern):
            patterns.append((pattern, func))
        else:
            pattern_obj = re.compile(pattern)
            patterns.append((pattern_obj, func))
        return func
    return _register


def filename(number):
    replace = {"one":1, "two":2, "three":3, "four":4, "five":5,
               "six":6, "seven":7, "eight":8, "nine":9, "ten":10,
               "eleven":11, "twelve":12, "thirteen":13, "fourteen":14}
    if number in replace:
        basename = "chapter_%02d" % replace[number]
    else:
        try:
            number_num = int(number)
            basename = "chapter_%02d" % number_num
        except ValueError:
            basename = "chapter_" + number.lower()
    basenames.append(basename)
    return basename + ".rst"


@handle("CHAPTER (.*)")
def chapter_title(line, match):
    global current_doc
    global is_first
    current_doc = file(filename(match.group(1).lower()), "w")
    is_first = True


@handle(r"--\d+/\d+--")
def skip_pagenumber(line, match):
    pass


@handle(r"[A-Z][A-Z ]+?[A-Z]\s+?(\d+)\n\Z")
def pagenumber1(line, match):
    current_doc.write("\n.. [ page %s ]\n\n" % match.group(1))


@handle(r"(\d+)\s+?[A-Z][A-Z ]+?[A-Z]\n\Z")
def pagenumber2(line, match):
    current_doc.write("\n.. [ page %s ]\n\n" % match.group(1))


@handle(r"[A-Z][A-Za-z ]+\s+?\|\s+?(\d+)\n\Z")
def pagenumber3(line, match):
    current_doc.write("\n.. [ page %s ]\n\n" % match.group(1))


@handle(r"(\d+)\s+?\|\s+?Chapter")
def pagenumber4(line, match):
    current_doc.write("\n.. [ page %s ]\n\n" % match.group(1))


def is_title(line):
    line = line.strip()
    if not line:
        return
    if line[0].islower() and line.endswith("."):
        return
    words = 0
    match = 0
    for word in line.split():
        if word.lower() in stop_words:
            continue
        words += 1
        if word[0].istitle():
            match += 1
    if words <= 3:
        return match == words
    else:
        return match >= words - 2


@handle(is_title)
def write_title(line, result):
    global is_first
    bar = "=" * (len(line)-1)
    if is_first:
        current_doc.write("\n.. %s\n   %s   %s\n" % (bar, line, bar))
        is_first = False
    else:
        current_doc.write("\n.. %s   %s\n" % (line, bar))


@handle("[A-Z]")
def start_paragraph(line, match):
    current_doc.write("\n.. %s" % line)


@handle(r"\? ")
def bullet_list(line, match):
    current_doc.write("\n.. * %s" % line.split("? ")[1])


def default_func(line):
    current_doc.write("   %s" % line.lstrip())


def main():
    for line in file("input.txt"):
        for pattern, func in patterns:
            if callable(pattern):
                result = pattern(line)
                if result:
                    func(line, result)
                    break
            else:
                match = pattern.match(line)
                if match:
                    func(line, match)
                    break
        else:
            default_func(line)


    index = file("index.rst", "w")

    index.write("""Title
=====

.. toctree::
   :maxdepth: 2
   :numbered:
   
   %s
""" % "\n   ".join(basenames))

if __name__ == "__main__":
    main()
posted by @shibukawa at 22:37 | Comment(22) | TrackBack(0) | Python はてなブックマーク - oreilly2sphinx
この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/36448307
※ブログオーナーが承認したトラックバックのみ表示されます。

この記事へのトラックバック
検索ボックス

Twitter

www.flickr.com
This is a Flickr badge showing public photos and videos from shibukawa.yoshiki. Make your own badge here.
<< 2017年05月 >>
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      
最近の記事
カテゴリ
過去ログ
Powered by さくらのブログ