なんとなくRustを勉強している中で、virshのxmlを編集する必要が出てきたため、ググって一番最初に見つかったquick_xmlを使ってみました。
quick_xmlは特徴としてRustのXMLライブラリの中でも特に早く動くらしいです。
cargo.tomlへの設定
0.20.0が最新っぽいのでこれを使います
[dependencies] quick-xml = "0.20.0"
実際に使ってみる
SYNOPSISを参考にtemplate.xmlからdump.xmlを作成する例題を書いてみました。
Readerはquick_xml専用の実装を使う必要があり、Reader::from_reader
に任意のReaderを渡すことで作成可能です。
let mut reader = Reader::from_reader(BufReader::new(File::open(file)?));
Writerも同様です。
let mut writer = Writer::new(BufWriter::new(File::create("dump.xml").unwrap()));
用意したReaderを使って実際にxmlを読んでみます。
公式の通りreader.read_event
で読み進めて、読んだ先の構造を型でマッチングする世界観のようです。
loop { match reader.read_event(&mut buf) { Ok(Event::Start(ref e)) if e.name() == XML_NAME_ATTRIBUTE => { writer .write_event(Event::Start(e.clone())) .expect("faild write event"); reader.read_event(&mut Vec::new()).expect("faild read event"); let elem = BytesText::from_plain_str("anatofuz-vm"); writer.write_event(Event::Text(elem)).unwrap(); } Ok(Event::Text(ref e)) if e.escaped() == b"ie-virsh-template" => { let elem = BytesText::from_plain_str("anatofuz-vm"); writer.write_event(Event::Text(elem)).unwrap(); } Ok(Event::End(ref e)) if e.name() == b"this_tag" => { assert!(writer .write_event(Event::End(BytesEnd::borrowed(b"my_elem"))) .is_ok()); } Ok(Event::Eof) => break, // you can use either `e` or `&e` if you don't want to move the event Ok(e) => writer.write_event(&e).unwrap(), Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e), } buf.clear(); }
この例ではEvent::Eof
が読まれるまで無限ループでxmlをパースし続けます。
各Event
は名前からなんとなくわかりますが、例えば<hoge>foo</hoge>
のような構造の場合は、<hoge>
がEvent::Start
、 foo
が Event::Text
、最後の</hoge>
がEvent::End
に対応します。
面白いのは後置のifのようなsyntaxで、特定のEventでかつ、特定の文字列が来た場合などの判定が可能なものです。
例えばこのブロックでは</this_tag>
のケースのみ実行されます。
Ok(Event::End(ref e)) if e.name() == b"this_tag" => { assert!(writer .write_event(Event::End(BytesEnd::borrowed(b"my_elem"))) .is_ok()); }
ここで<name>template</name>
のようなXMLの構造が来た場合に、中のtemplate
を書き換える処理を考えてみます。
まず<name>
で来た場合にキャプチャをする必要があるので、Event::Start
でキャプチャを行います。
const XML_NAME_ATTRIBUTE: &[u8; 4] = b"name"; ///省略 Ok(Event::Start(ref e)) if e.name() == XML_NAME_ATTRIBUTE => {
writerの先のxmlへはwriter.write_event
など、型によってwriter_*
の専用メソッドが用意されています。
この例ではEvent
単位で読みながらファイルを書き出していくので、基本的にはwriter.write_event
を使用することになります。
writerへの書き込みはref e
で受けていた場合はe.clone()
する必要があり、ref
で受けていない場合はポインタを取るのみで可能です。
writer .write_event(Event::Start(e.clone())) .expect("faild write event");
次に<name>template</name>
のtemplate
を書き換える為に、read_event
で更に1EVENT読み進めます。
reader.read_event(&mut Vec::new()).expect("faild read event");
読んだ先は使わないので特に束縛せず、雑にVec::new()
しています。
続いて一度BytesText
のインスタンスを作ってから、Event::Text
型を作り、これをwrite_event
経由で書き込みます。
文字列リテラルからのBytesText
の作成はfrom_plain_str
を使うと可能です。
let elem = BytesText::from_plain_str("anatofuz-vm"); writer.write_event(Event::Text(elem)).unwrap();
そもそも、xmlのcontentを決め打ちでリプレイスする場合は、Event::Text
でマッチングすると楽です。
Ok(Event::Text(ref e)) if e.escaped() == b"ie-virsh-template" => { let mut elem = BytesText::from_plain_str("anatofuz-vm"); writer.write_event(Event::Text(elem)); }