actix-webではやくWebアプリを作りたいオウルです。あと、RustでWebAssembly したい。今回は、意外に使う頻度が多いXMLパースをRustで実装です。
Rustの学習にあたり、僕は公式ドキュメント以外に次の2冊の入門書を参考にしています。どちらも分かり易くおすすめです。この2冊が情報のソースとなっているところがあるので、後で分かり易いようにページ番号を残すようにします。
language | Rust 1.51.0 |
os | WSL Ubuntu 21.04 |
editor | VSCode |
XMLパース
読み込むXMLは、Microsoftのドキュメントサンプルを少し加工したXMLを使用します。
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
<?xml version="1.0"?> <catalog> <book id="bk101" genre="computer"> <author>Gambardella, Matthew</author> <title>XML Developer's Guide</title> <price>44.95</price> <publish_date>2000-10-01</publish_date> <description>An in-depth look at creating applications with XML.</description> </book> <book id="bk102" genre="fantasy"> <author>Ralls, Kim</author> <title>Midnight Rain</title> <price>5.95</price> <publish_date>2000-12-16</publish_date> <description>A former architect battles corporate zombies, an evil sorceress, and her own childhood to become queen of the world.</description> </book> <book id="bk103"> <author>Corets, Eva</author> <title>Maeve Ascendant</title> <genre>Fantasy</genre> <price>5.95</price> <publish_date>2000-11-17</publish_date> <description>After the collapse of a nanotechnology society in England, the young survivors lay the foundation for a new society.</description> </book> <book id="bk104"> <author>Corets, Eva</author> <title>Oberon's Legacy</title> <genre>Fantasy</genre> <price>5.95</price> <publish_date>2001-03-10</publish_date> <description>In post-apocalypse England, the mysterious agent known only as Oberon helps to create a new life for the inhabitants of London. Sequel to Maeve Ascendant.</description> </book> <book id="bk105"> <author>Corets, Eva</author> <title>The Sundered Grail</title> <genre>Fantasy</genre> <price>5.95</price> <publish_date>2001-09-10</publish_date> <description>The two daughters of Maeve, half-sisters, battle one another for control of England. Sequel to Oberon's Legacy.</description> </book> <book id="bk106"> <author>Randall, Cynthia</author> <title>Lover Birds</title> <genre>Romance</genre> <price>4.95</price> <publish_date>2000-09-02</publish_date> <description>When Carla meets Paul at an ornithology conference, tempers fly as feathers get ruffled.</description> </book> <book id="bk107"> <author>Thurman, Paula</author> <title>Splish Splash</title> <genre>Romance</genre> <price>4.95</price> <publish_date>2000-11-02</publish_date> <description>A deep sea diver finds true love twenty thousand leagues beneath the sea.</description> </book> </catalog> |
roxmltree
crate registryのparserキーワードにあるRead-Onlyなroxmltreeをパーサーとして使用します。
パース
サンプルのXMLは、Windows環境下に保存してShift_JISとします。パースするにあたり、XMLファイルの中身を文字列として処理します。Rustでは文字列を扱うのに、&str(文字列スライス、借用)、String(文字列型、所有権あり)がよく使用され、これらはUTF-8エンコードされた文字列を取り扱います。
そこで[u8]型で格納されたバイト列をencoding_rsを使ってエンコーディング変換を行います。因みにencoding_rsは「実践Rustプログラミング入門」(P186)で紹介されていたクレートです。
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
use std::fs; use std::path::Path; use anyhow::{ Context }; use chrono::{ NaiveDate }; pub fn xml_parse<T: AsRef<Path>>(path: T) -> anyhow::Result<Vec<Book>> { let s = fs::read(path.as_ref()).with_context(|| format!("not read {:?}", path.as_ref().to_str()))?; // 下記のExamplesにあるように非ストリーミングAPIを使用してデコードします // Examples: https://docs.rs/encoding_rs/0.8.28/encoding_rs/#examples // Cowというスマートポインタで結果を受け取ります let (res, _, _) = encoding_rs::SHIFT_JIS.decode(&s); // クローンして所有します let contents = res.into_owned(); let opt = roxmltree::ParsingOptions::default(); let doc = roxmltree::Document::parse_with_options(&contents, opt).unwrap(); let node = doc.root_element(); let books: Vec<Book> = node.children() .filter(|n| n.tag_name().name() == "book" && n.attribute("genre").unwrap_or("") == "fantasy") .map(|n| { let author = get_text(n, "author"); let title = get_text(n, "title"); let price = get_text(n, "price").parse::<f32>().unwrap_or(0.0); let publish_date = NaiveDate::parse_from_str(&get_text(n, "publish_date"), "%Y-%m-%d").unwrap_or(NaiveDate::from_ymd(0000, 01, 01)); let description = get_text(n, "description"); Book { author, title, price, publish_date, description, } }) .collect(); Ok(books) } fn get_text(node: roxmltree::Node, tag_name: impl AsRef<str>) -> String { if node.has_children() { let cn = node.children().filter(|n| n.tag_name().name() == tag_name.as_ref()).next(); match cn { Some(n) => n.text().unwrap().to_string(), None => String::from(""), } } else { String::from("") } } #[derive(Debug)] pub struct Book { pub author: String, pub title: String, pub price: f32, pub publish_date: NaiveDate, pub description: String, } |
ここは、どちらかというとroxmltreeの使い方がほとんどなので、使い方以外のポイントを書いておきます。
std::convert::AsRef
xml_parseメソッド定義のシグニチャを見てみると、前回の記事で紹介したstd::convert::AsRefを使用しています。
std::borrow::Cow
デコードの結果をstd::borrow::Cowというクローンオンライト スマートポインタで受け取っています。
std::borrow::Cowに詳しくExamples付きで説明がありますが、理解するところ、借用をラップして不変アクセスを可能にして、変更・所有権が必要な場合は遅延クローンする、ということでしょうか。因みに、Examplesにあるように、既に所有している場合はクローンされません。
ここでは、into_ownedでクローンして所有しています。余談ですが、into_ownedメソッド定義のシグネチャにあるフルパス記法が思い出せなかった。
Cowはstd::borrow::Borrowトレイトを介して一般的な借用データを処理するように設計されているようなので、std::borrow::Borrowトレイトの一歩踏み込んだ理解が必要かな。
次はパースした結果をCSVに出力することにチャレンジです。