なぜかRustで言語処理100本ノック ～第3章 後編～

[前編はこちら](https://qiita.com/kbone/items/9b62f365c081c09ae39f)

# 第3章: 正規表現
前編に加え、URLエンコード用にpercent_encodingを、(rustのライブラリの)curlを用いております。
## 25. テンプレートの抽出
> 記事中に含まれる「基礎情報」テンプレートのフィールド名と値を抽出し，辞書オブジェクトとして格納せよ．

かなり難しかったです。というのもテンプレートの形が、`{{テンプレート名|key1=value1|key2=value2}}`という形なのですが、この`value`のところに`|`(縦棒)や`}}`が入る場合があるようで、汎用的にしようと思うとどうしても構文解析的なことをする必要があり、正規表現では書けなかったのです。ですが、今回の場合に限ると`|`(縦棒)は必ず行の先頭で、最後の`}}`はこれだけで単独の行に存在していたので、その仮定のもとに書くとなんとか抽出できました。前編を書くのに時間がかかったのは(最初はここまで前編に入れようとしていたので)これが原因です。

```rust
pub fn templates(article: &Article, template_name: &str) -> HashMap<String, String> {
    let wide_regex = Regex::new(&(r"(?sm)^\{\{".to_owned() + template_name + r"\s*(.*)^}}$")).unwrap();
    let narrow_regex = Regex::new(r"(?sm)^\|\s*([^\n]*)\s*=\s*([^\n]*)\n(.*)").unwrap();
    let mut result = HashMap::new();
    wide_regex.captures_iter(&article.text)
        .map(|captures| captures.get(1).unwrap().as_str())
        .for_each(|text| {
            let mut text = text;
            while narrow_regex.is_match(text) {
                narrow_regex.captures_iter(&text).for_each(|captures| {
                    result.insert(captures.get(1).unwrap().as_str().to_string(), captures.get(2).unwrap().as_str().to_string());
                    text = captures.get(3).unwrap().as_str();
                });
            }
        });
    result
}
```

## 26. 強調マークアップの除去
> 25の処理時に，テンプレートの値からMediaWikiの強調マークアップ（弱い強調，強調，強い強調のすべて）を除去してテキストに変換せよ（参考: [マークアップ早見表](https://ja.wikipedia.org/wiki/Help:%E6%97%A9%E8%A6%8B%E8%A1%A8)）．

処理時にと書いてありますが、処理後でも大して変わらないので、処理後に行っています。また、本来は`''`と`'''`と`'''''`だけですが、`''''`を除去しても変わらないはずなので、正規表現でまとめて`'{2,5}`としてあります。

```rust
pub fn remove_strong(templates: &HashMap<String, String>) -> HashMap<String, String> {
    let regex = Regex::new(r"'{2,5}").unwrap();
    templates.iter().map(|(k, v)| {
        (k.clone(), regex.replace_all(v, "").into())
    }).collect()
}
```

## 27. 内部リンクの除去
> 26の処理に加えて，テンプレートの値からMediaWikiの内部リンクマークアップを除去し，テキストに変換せよ（参考: [マークアップ早見表](https://ja.wikipedia.org/wiki/Help:%E6%97%A9%E8%A6%8B%E8%A1%A8)）．

記号だらけになりましたが、良く見てみると結構単純です。`regex.replace_all`の第2引数の`$1`は1つ目のキャプチャ部分という意味です。

```rust
pub fn remove_link(templates: &HashMap<String, String>) -> HashMap<String, String> {
    let regex = Regex::new(r"\[\[(?:[^|\]]*\|)*([^|\]]*)\]\]").unwrap();
    templates.iter().map(|(k, v)| {
        (k.clone(), regex.replace_all(v, "$1").into())
    }).collect()
}
```

## 28. MediaWikiマークアップの除去
この問題は26と27でやったことを繰り返すだけなので、省略します。

## 29. 国旗画像のURLを取得する
> テンプレートの内容を利用し，国旗画像のURLを取得せよ．（ヒント: [MediaWiki API](http://www.mediawiki.org/wiki/API:Main_page/ja)の[imageinfo](http://www.mediawiki.org/wiki/API:Properties/ja#imageinfo_.2F_ii)を呼び出して，ファイル参照をURLに変換すればよい）

何故かデータが複数回に分けられて送られてきたため、その処理に手間取りましたが、最終的には案外スッキリと書けました。ただ、マジックナンバーを避けようとするとどうしても長くなってしまいます。

```rust
pub fn get_image(file_name: &str) -> String {
    let mut curl = Easy2::new(Collector(Vec::new()));
    curl.get(true).unwrap();
    curl.url(&("https://commons.wikimedia.org/w/api.php?action=query&titles=File:".to_string()
             + &percent_encoding::utf8_percent_encode(file_name, percent_encoding::DEFAULT_ENCODE_SET).to_string()
             + "&prop=imageinfo&iiprop=url&format=json")).unwrap();
    curl.perform().unwrap();
    let collected = curl.get_ref();
    let json: Value = serde_json::from_slice(&collected.0).unwrap();
    json["query"]["pages"].as_object().unwrap().values().next().unwrap()["imageinfo"][0]["url"].to_string()
}
```

# 第3章の感想
この章は何かと苦労した気がします。
後編だけ見ても、意地悪な正規表現に始まり、serde_jsonの扱いに慣れていない部分と、更に最後には何故か複数回に分けて送られてくるデータという様々な扱いにくさを感じました。serde_jsonの部分はそれでも最後の方には少し慣れてきましたが。