rust で XPath できる crate たちのメモ; (1) amxml, (2) sxd-xpath
XPath 使いたい需要に対応できる XML パーサーを crates.io で探すと有用そうな crate が2つ見つかりました。簡単な XPath で使い勝手を確認したメモです。
- amxml
- sxd-xpath ( + sxd-document )
1. amxml
cargo add amxml
#[cfg(test)] mod tests { /// 共通: 入力XML const INPUT: &str = "<abc><x/><y/><z/></abc>"; /// ルートノードの名前をXPathで確認 #[test] fn amxml_root() { let document = amxml::dom::new_document(INPUT).unwrap(); let xpath_result = document.eval_xpath("/*"); let root_name = xpath_result.unwrap().get_item(0).as_nodeptr().unwrap().name(); assert_eq!(root_name, "abc"); } /// 第2階層(=ルート直下)ノード群の名前をXPathで確認 #[test] fn amxml_sec() { let mut sec_names: Vec<String> = vec![]; let document = amxml::dom::new_document(INPUT).unwrap(); document .each_node("/*/*", |node| { sec_names.push(node.name().clone()); }) .unwrap(); assert_eq!(sec_names, ["x", "y", "z"]); } }
- 👍 嬉しいところ:
- ⚠ 注意が必要なところ:
// 参考おまけ: note(†) let xpath_result = document.eval_xpath("/*/name()"); // XPath では /name() で要素名の文字列値を取れる let root_name = xpath_result.unwrap().get_item(0).to_string(); // XPathの結果がノードではなく文字列値なので Item::to_string を使える assert_eq!(root_name, "\"abc\""); // 文字列リテラル表現で取れてくるので比較など後の処理で"文字列リテラルな文字列"を気にする事になります
2. sxd-xpath ( + sxd-document )
cargo add sxd-xpath sxd-document
#[cfg(test)] mod tests { /// 共通: 入力XML const INPUT: &str = "<abc><x/><y/><z/></abc>"; /// ルートノードの名前をXPathで確認 #[test] fn sxd_root() { let package = sxd_document::parser::parse(INPUT).unwrap(); let document = package.as_document(); let xpath_result = sxd_xpath::evaluate_xpath(&document, "/*"); let value = xpath_result.unwrap(); let root_name = match value { sxd_xpath::Value::Nodeset(nodes) => { let node = nodes.iter().next().unwrap(); let qname = node.expanded_name().unwrap(); qname.local_part() }, _ => panic!("Failed: `Value` -> `Nodeset`") }; assert_eq!(root_name, "abc"); } /// 第2階層(=ルート直下)ノード群の名前をXPathで確認 #[test] fn sxd_sec() { let package = sxd_document::parser::parse(INPUT).unwrap(); let document = package.as_document(); let xpath_result = sxd_xpath::evaluate_xpath(&document, "/*/*"); let value = xpath_result.unwrap(); let sec_names: Vec<String> = match value { sxd_xpath::Value::Nodeset(nodes) => { nodes .iter() .map(|node| node.expanded_name().unwrap().local_part().to_string()) .collect() }, _ => panic!("Failed: `Value` -> `Nodeset`") }; // ☢ 要注意: sxd-xpath の nodes の列挙順序は実行ごとに変わります。 let expected = ["x", "y", "z"]; assert_eq!(sec_names.len(), expected.len()); for element in expected.iter() { assert!(sec_names.contains(&element.to_string())); } } }
- 👍 嬉しいところ:
- 更新頻度、コミッター数はぼちぼち
- ❓ 嬉しいかもしれないけれど判断の難しいところ:
- めんどくさいので触れませんでしたが
Context
を使うとより変態的な実装もできそうです
- めんどくさいので触れませんでしたが
- ⚠ 注意が必要なところ:
どちらを使うのが嬉しそうか?
- XPath-3.1 対応が欲しい 👉 amxml
- Author / Comitter が活きていて欲しい 👉 sxd-xpath
- ユーザーコードを簡潔に済ませたい 👉 amxml
- XPath 以外の XML の DOM アクセスや read だけでなく write も欲しい 👉 sxd-xpath > amxml (基本的なDOM操作はどちらでもできます)
追記: どちらが速そう?; 2020-08-05
↓は
test benches::amxml_root ... bench: 37,902 ns/iter (+/- 4,120) test benches::amxml_sec ... bench: 25,728 ns/iter (+/- 1,255) test benches::sxd_root ... bench: 13,392 ns/iter (+/- 2,052) test benches::sxd_sec ... bench: 16,544 ns/iter (+/- 2,413)