taken by caribb under CC BY-NC-ND
動物本と言えばオライリー。オライリーと言えば動物本。良質な洋書を翻訳した本が多く(書き下ろしもあります)、品質は折り紙付きな出版社です。オライリーの本に接する人は大きく二つに分かれると思います:
- 本を読む人
- 洋書を翻訳する人
このブログで紹介するスクリプトは、後者の人で、尚かつ翻訳にSphinxを使うよ!というレアな人向けです。適当に作ってみたのですが、便利そうなので公開します。テストは手元にあったThe Art of Community
とErlang 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()