---
title: "mdBook：プリプロセッサー"
documentId: "mdbook:guide/src/for_developers/preprocessors.md"
order: 29
licenseSource: "mdbook-guide"
documentContext: [{"kind": "source", "html": "<p>mdBook 0.5.4公式文書の非公式日本語訳。文書とその翻訳はMPL-2.0。<a href=\"https://github.com/rust-lang/mdBook/blob/2ea30c00f00647d2b3f4c0f79b3e0e1eabc0b66d/guide/src/for_developers/preprocessors.md\">固定した原典</a> · <a href=\"/docs/mdbook/source/v0-5-4/edited/ja/13-for_developers-preprocessors.md\">編集可能な日本語文書</a> · <a href=\"/docs/mdbook/source/v0-5-4/mdbook-original.tar.gz\">固定原資料一式</a>。 · <a href=\"/docs/mdbook/source/v0-5-4/source.zip\">編集可能な英日原稿とビルド資料</a>.</p>"}, {"kind": "editorial", "html": "<p>Libxでは全文・コード・図・原目次を静的に提供します。原著の編集・実行機能は原典を参照してください。コードの非表示行は全表示し、数式は原TeX表記を保持します。章の操作説明はmdBookが生成した元の本を対象とします。</p>"}]
prev: {"text": "開発者向け", "link": "/v0-5-4/ja/01-guide/11-for_developers-index"}
next: {"text": "代替バックエンド", "link": "/v0-5-4/ja/01-guide/12-for_developers-backends"}
---


<div class="mdbook-guide">
<h1 id="preprocessors"><a class="header" href="#preprocessors">プリプロセッサー</a></h1>
<p><em>プリプロセッサー</em>は、本の読み込み直後、出力生成の前に実行されるコードです。本を更新・変更できます。たとえば、次のように使えます。</p>
<ul>
<li><code>{{#include /path/to/file.md}}</code>のような独自のヘルパーを作る</li>
<li>LaTeX形式の式（<code>$$ \frac{1}{3} $$</code>）を、対応するMathJaxの式へ置き換える</li>
</ul>
<p>プリプロセッサーの使い方について、詳しくは<a href="/docs/mdbook/v0-5-4/ja/01-guide/18-format-configuration-preprocessors/">プリプロセッサーの設定</a>を参照してください。</p>
<h2 id="hooking-into-mdbook"><a class="header" href="#hooking-into-mdbook">MDBookへ組み込む</a></h2>
<p>MDBookが第三者のプラグインを見つける仕組みは簡単です。<code>book.toml</code>へ新しいテーブル（<code>foo</code>プリプロセッサーなら<code>[preprocessor.foo]</code>）を追加すると、<code>mdbook</code>はビルド処理の中で<code>mdbook-foo</code>プログラムの呼び出しを試みます。</p>
<p>プリプロセッサーを定義し、ビルド処理を開始すると、mdBookは<code>preprocessor.foo.command</code>キーで定義したコマンドを2回実行します。1回目は、指定したレンダラーに対応するかを確認します。プロセスへ2つの引数を渡します。最初は文字列<code>supports</code>、次はレンダラーの名前です。プリプロセッサーは、対応する場合は終了コード0、対応しない場合は0以外で終了する必要があります。</p>
<p>レンダラーに対応する場合、mdbookは2回目を実行し、stdinへJSONデータを渡します。JSONは<code>[context, book]</code>という配列です。<code>context</code>はシリアライズした<a href="https://docs.rs/mdbook-preprocessor/latest/mdbook_preprocessor/struct.PreprocessorContext.html"><code>PreprocessorContext</code></a>オブジェクト、<code>book</code>は本の内容を含む<a href="https://docs.rs/mdbook-preprocessor/latest/mdbook_preprocessor/book/struct.Book.html"><code>Book</code></a>オブジェクトです。</p>
<p>プリプロセッサーは、任意の変更を加えた<a href="https://docs.rs/mdbook-preprocessor/latest/mdbook_preprocessor/book/struct.Book.html"><code>Book</code></a>オブジェクトを、JSON形式でstdoutへ返す必要があります。</p>
<p>最も簡単な始め方は、独自の<code>Preprocessor</code>トレイト実装を作り（たとえば<code>lib.rs</code>へ）、入力を適切な<code>Preprocessor</code>メソッドへ振り分ける、外側の実行ファイルを作る方法です。<code>examples/</code>ディレクトリには、ほかのプリプロセッサーへ簡単に応用できる<a href="https://github.com/rust-lang/mdBook/blob/master/examples/nop-preprocessor.rs">何もしないプリプロセッサーの例</a>があります。</p>
<details>
<summary>何もしないプリプロセッサーの例</summary>
<pre class="playground"><code class="language-rust edition2018">// nop-preprocessors.rs&#10;&#10;//! A basic example of a preprocessor that does nothing.&#10;&#10;use crate::nop_lib::Nop;&#10;use clap::{Arg, ArgMatches, Command};&#10;use mdbook_preprocessor::book::Book;&#10;use mdbook_preprocessor::errors::Result;&#10;use mdbook_preprocessor::{Preprocessor, PreprocessorContext};&#10;use semver::{Version, VersionReq};&#10;use std::io;&#10;use std::process;&#10;&#10;fn make_app() -> Command {&#10;    Command::new("nop-preprocessor")&#10;        .about("A mdbook preprocessor which does precisely nothing")&#10;        .subcommand(&#10;            Command::new("supports")&#10;                .arg(Arg::new("renderer").required(true))&#10;                .about("Check whether a renderer is supported by this preprocessor"),&#10;        )&#10;}&#10;&#10;fn main() {&#10;    let matches = make_app().get_matches();&#10;&#10;    // Users will want to construct their own preprocessor here&#10;    let preprocessor = Nop::new();&#10;&#10;    if let Some(sub_args) = matches.subcommand_matches("supports") {&#10;        handle_supports(&#x26;preprocessor, sub_args);&#10;    } else if let Err(e) = handle_preprocessing(&#x26;preprocessor) {&#10;        eprintln!("{e:?}");&#10;        process::exit(1);&#10;    }&#10;}&#10;&#10;fn handle_preprocessing(pre: &#x26;dyn Preprocessor) -> Result&#x3C;()> {&#10;    let (ctx, book) = mdbook_preprocessor::parse_input(io::stdin())?;&#10;&#10;    let book_version = Version::parse(&#x26;ctx.mdbook_version)?;&#10;    let version_req = VersionReq::parse(mdbook_preprocessor::MDBOOK_VERSION)?;&#10;&#10;    if !version_req.matches(&#x26;book_version) {&#10;        eprintln!(&#10;            "Warning: The {} plugin was built against version {} of mdbook, \&#10;             but we're being called from version {}",&#10;            pre.name(),&#10;            mdbook_preprocessor::MDBOOK_VERSION,&#10;            ctx.mdbook_version&#10;        );&#10;    }&#10;&#10;    let processed_book = pre.run(&#x26;ctx, book)?;&#10;    serde_json::to_writer(io::stdout(), &#x26;processed_book)?;&#10;&#10;    Ok(())&#10;}&#10;&#10;fn handle_supports(pre: &#x26;dyn Preprocessor, sub_args: &#x26;ArgMatches) -> ! {&#10;    let renderer = sub_args&#10;        .get_one::&#x3C;String>("renderer")&#10;        .expect("Required argument");&#10;    let supported = pre.supports_renderer(renderer).unwrap();&#10;&#10;    // Signal whether the renderer is supported by exiting with 1 or 0.&#10;    if supported {&#10;        process::exit(0);&#10;    } else {&#10;        process::exit(1);&#10;    }&#10;}&#10;&#10;/// The actual implementation of the `Nop` preprocessor. This would usually go&#10;/// in your main `lib.rs` file.&#10;#&#91;allow(unreachable_pub, reason = "wouldn't be a problem in a proper lib.rs")&#93;&#10;mod nop_lib {&#10;    use super::*;&#10;&#10;    /// A no-op preprocessor.&#10;    pub struct Nop;&#10;&#10;    impl Nop {&#10;        pub fn new() -> Nop {&#10;            Nop&#10;        }&#10;    }&#10;&#10;    impl Preprocessor for Nop {&#10;        fn name(&#x26;self) -> &#x26;str {&#10;            "nop-preprocessor"&#10;        }&#10;&#10;        fn run(&#x26;self, ctx: &#x26;PreprocessorContext, book: Book) -> Result&#x3C;Book> {&#10;            // In testing we want to tell the preprocessor to blow up by setting a&#10;            // particular config value&#10;            match ctx&#10;                .config&#10;                .get::&#x3C;bool>("preprocessor.nop-preprocessor.blow-up")&#10;            {&#10;                Ok(Some(true)) => anyhow::bail!("Boom!!1!"),&#10;                Ok(_) => {}&#10;                Err(e) => anyhow::bail!("expect bool for blow-up: {e}"),&#10;            }&#10;&#10;            // we *are* a no-op preprocessor after all&#10;            Ok(book)&#10;        }&#10;&#10;        fn supports_renderer(&#x26;self, renderer: &#x26;str) -> Result&#x3C;bool> {&#10;            Ok(renderer != "not-supported")&#10;        }&#10;    }&#10;&#10;    #&#91;cfg(test)&#93;&#10;    mod test {&#10;        use super::*;&#10;&#10;        #&#91;test&#93;&#10;        fn nop_preprocessor_run() {&#10;            let input_json = r##"&#91;&#10;                {&#10;                    "root": "/path/to/book",&#10;                    "config": {&#10;                        "book": {&#10;                            "authors": &#91;"AUTHOR"&#93;,&#10;                            "language": "en",&#10;                            "src": "src",&#10;                            "title": "TITLE"&#10;                        },&#10;                        "preprocessor": {&#10;                            "nop": {}&#10;                        }&#10;                    },&#10;                    "renderer": "html",&#10;                    "mdbook_version": "0.4.21"&#10;                },&#10;                {&#10;                    "items": &#91;&#10;                        {&#10;                            "Chapter": {&#10;                                "name": "Chapter 1",&#10;                                "content": "# Chapter 1\n",&#10;                                "number": &#91;1&#93;,&#10;                                "sub_items": &#91;&#93;,&#10;                                "path": "chapter_1.md",&#10;                                "source_path": "chapter_1.md",&#10;                                "parent_names": &#91;&#93;&#10;                            }&#10;                        }&#10;                    &#93;&#10;                }&#10;            &#93;"##;&#10;            let input_json = input_json.as_bytes();&#10;&#10;            let (ctx, book) = mdbook_preprocessor::parse_input(input_json).unwrap();&#10;            let expected_book = book.clone();&#10;            let result = Nop::new().run(&#x26;ctx, book);&#10;            assert!(result.is_ok());&#10;&#10;            // The nop-preprocessor should not have made any changes to the book content.&#10;            let actual_book = result.unwrap();&#10;            assert_eq!(actual_book, expected_book);&#10;        }&#10;    }&#10;}</code></pre>
</details>
<h2 id="hints-for-implementing-a-preprocessor"><a class="header" href="#hints-for-implementing-a-preprocessor">プリプロセッサーを実装するヒント</a></h2>
<p><code>mdbook-preprocessor</code>をライブラリーとして取り込むと、本を扱う既存の仕組みをプリプロセッサーから使えます。</p>
<p>たとえば、独自プリプロセッサーでは、<a href="https://docs.rs/mdbook-preprocessor/latest/mdbook_preprocessor/fn.parse_input.html"><code>parse_input()</code></a>関数で、<code>stdin</code>へ渡されたJSONをデシリアライズできます。続いて、<a href="https://docs.rs/mdbook-preprocessor/latest/mdbook_preprocessor/book/struct.Book.html#method.for_each_mut"><code>Book::for_each_mut()</code></a>で<code>Book</code>の各章をその場で変更し、<code>serde_json</code>クレートで<code>stdout</code>へ書き出せます。</p>
<p>章には、再帰的に順にたどって直接アクセスする方法と、便利な<code>Book::for_each_mut()</code>メソッドを使う方法があります。</p>
<p><code>chapter.content</code>は、Markdownを含む文字列にすぎません。正規表現や手作業の検索・置換でも処理できますが、コンピューターで扱いやすい形式へ変換するとよいでしょう。<a href="https://docs.rs/mdbook-markdown/latest/mdbook_markdown/"><code>mdbook-markdown</code></a>クレートは、mdBookがMarkdownの解析に使う<a href="https://crates.io/crates/pulldown-cmark"><code>pulldown-cmark</code></a>クレートを公開しています。<a href="https://crates.io/crates/pulldown-cmark-to-cmark"><code>pulldown-cmark-to-cmark</code></a>クレートで、イベントをMarkdownのテキストへ戻せます。</p>
<p>次のコードブロックは、文書を意図せず壊さずに、Markdownの強調をすべて取り除く方法を示します。</p>
<pre class="playground"><code class="language-rust edition2018"><span data-mdbook-hidden-line="true">//! This is a demonstration of an mdBook preprocessor which parses markdown&#10;</span><span data-mdbook-hidden-line="true">//! and removes any instances of emphasis.&#10;</span><span data-mdbook-hidden-line="true">&#10;</span><span data-mdbook-hidden-line="true">use mdbook_preprocessor::book::{Book, Chapter};&#10;</span><span data-mdbook-hidden-line="true">use mdbook_preprocessor::errors::Result;&#10;</span><span data-mdbook-hidden-line="true">use mdbook_preprocessor::{Preprocessor, PreprocessorContext};&#10;</span><span data-mdbook-hidden-line="true">use pulldown_cmark::{Event, Parser, Tag, TagEnd};&#10;</span><span data-mdbook-hidden-line="true">use std::io;&#10;</span><span data-mdbook-hidden-line="true">&#10;</span><span data-mdbook-hidden-line="true">fn main() {&#10;</span><span data-mdbook-hidden-line="true">    let mut args = std::env::args().skip(1);&#10;</span><span data-mdbook-hidden-line="true">    match args.next().as_deref() {&#10;</span><span data-mdbook-hidden-line="true">        Some("supports") => {&#10;</span><span data-mdbook-hidden-line="true">            // Supports all renderers.&#10;</span><span data-mdbook-hidden-line="true">            return;&#10;</span><span data-mdbook-hidden-line="true">        }&#10;</span><span data-mdbook-hidden-line="true">        Some(arg) => {&#10;</span><span data-mdbook-hidden-line="true">            eprintln!("unknown argument: {arg}");&#10;</span><span data-mdbook-hidden-line="true">            std::process::exit(1);&#10;</span><span data-mdbook-hidden-line="true">        }&#10;</span><span data-mdbook-hidden-line="true">        None => {}&#10;</span><span data-mdbook-hidden-line="true">    }&#10;</span><span data-mdbook-hidden-line="true">&#10;</span><span data-mdbook-hidden-line="true">    if let Err(e) = handle_preprocessing() {&#10;</span><span data-mdbook-hidden-line="true">        eprintln!("{e}");&#10;</span><span data-mdbook-hidden-line="true">        std::process::exit(1);&#10;</span><span data-mdbook-hidden-line="true">    }&#10;</span><span data-mdbook-hidden-line="true">}&#10;</span><span data-mdbook-hidden-line="true">&#10;</span><span data-mdbook-hidden-line="true">struct RemoveEmphasis;&#10;</span><span data-mdbook-hidden-line="true">&#10;</span><span data-mdbook-hidden-line="true">impl Preprocessor for RemoveEmphasis {&#10;</span><span data-mdbook-hidden-line="true">    fn name(&#x26;self) -> &#x26;str {&#10;</span><span data-mdbook-hidden-line="true">        "remove-emphasis"&#10;</span><span data-mdbook-hidden-line="true">    }&#10;</span><span data-mdbook-hidden-line="true">&#10;</span><span data-mdbook-hidden-line="true">    fn run(&#x26;self, _ctx: &#x26;PreprocessorContext, mut book: Book) -> Result&#x3C;Book> {&#10;</span><span data-mdbook-hidden-line="true">        let mut total = 0;&#10;</span><span data-mdbook-hidden-line="true">        book.for_each_chapter_mut(|ch| match remove_emphasis(&#x26;mut total, ch) {&#10;</span><span data-mdbook-hidden-line="true">            Ok(s) => ch.content = s,&#10;</span><span data-mdbook-hidden-line="true">            Err(e) => eprintln!("failed to process chapter: {e:?}"),&#10;</span><span data-mdbook-hidden-line="true">        });&#10;</span><span data-mdbook-hidden-line="true">        eprintln!("removed {total} emphasis");&#10;</span><span data-mdbook-hidden-line="true">        Ok(book)&#10;</span><span data-mdbook-hidden-line="true">    }&#10;</span><span data-mdbook-hidden-line="true">}&#10;</span><span data-mdbook-hidden-line="true">&#10;</span>fn remove_emphasis(num_removed_items: &#x26;mut usize, chapter: &#x26;mut Chapter) -> Result&#x3C;String> {&#10;    let mut buf = String::with_capacity(chapter.content.len());&#10;&#10;    let events = Parser::new(&#x26;chapter.content).filter(|e| match e {&#10;        Event::Start(Tag::Emphasis) | Event::Start(Tag::Strong) => {&#10;            *num_removed_items += 1;&#10;            false&#10;        }&#10;        Event::End(TagEnd::Emphasis) | Event::End(TagEnd::Strong) => false,&#10;        _ => true,&#10;    });&#10;&#10;    Ok(pulldown_cmark_to_cmark::cmark(events, &#x26;mut buf).map(|_| buf)?)&#10;}&#10;<span data-mdbook-hidden-line="true">&#10;</span><span data-mdbook-hidden-line="true">pub fn handle_preprocessing() -> Result&#x3C;()> {&#10;</span><span data-mdbook-hidden-line="true">    let pre = RemoveEmphasis;&#10;</span><span data-mdbook-hidden-line="true">    let (ctx, book) = mdbook_preprocessor::parse_input(io::stdin())?;&#10;</span><span data-mdbook-hidden-line="true">&#10;</span><span data-mdbook-hidden-line="true">    let processed_book = pre.run(&#x26;ctx, book)?;&#10;</span><span data-mdbook-hidden-line="true">    serde_json::to_writer(io::stdout(), &#x26;processed_book)?;&#10;</span><span data-mdbook-hidden-line="true">&#10;</span><span data-mdbook-hidden-line="true">    Ok(())&#10;</span><span data-mdbook-hidden-line="true">}</span></code></pre>
<p>詳しくは<a href="https://github.com/rust-lang/mdBook/tree/master/examples/remove-emphasis/">例の完全なソース</a>を参照してください。</p>
<h2 id="implementing-a-preprocessor-with-a-different-language"><a class="header" href="#implementing-a-preprocessor-with-a-different-language">別の言語でプリプロセッサーを実装する</a></h2>
<p>mdBookはstdinとstdoutでプリプロセッサーと通信するため、Rust以外の言語でも簡単に実装できます。次のコードは、最初の章の内容を変更する、Pythonによる簡単なプリプロセッサーの実装を示します。上で示した設定に従い、<code>preprocessor.foo.command</code>がPythonスクリプトを指すものとします。</p>
<pre><code class="language-python">import json&#10;import sys&#10;&#10;&#10;if __name__ == '__main__':&#10;    if len(sys.argv) > 1: # we check if we received any argument&#10;        if sys.argv&#91;1&#93; == "supports": &#10;            # then we are good to return an exit status code of 0, since the other argument will just be the renderer's name&#10;            sys.exit(0)&#10;&#10;    # load both the context and the book representations from stdin&#10;    context, book = json.load(sys.stdin)&#10;    # and now, we can just modify the content of the first chapter&#10;    book&#91;'items'&#93;&#91;0&#93;&#91;'Chapter'&#93;&#91;'content'&#93; = '# Hello'&#10;    # we are done with the book's modification, we can just print it to stdout, &#10;    print(json.dumps(book))&#10;</code></pre>
</div>
