我很难以使用rust 的方式来制定这个,因为我的大脑仍然在 Python 中硬连线。所以我有一个 XML 文件:
<xml>
<car>
<name>First car</name>
<brand>Volvo</brand>
</car>
<plane>
<name>First plane</name>
<brand>Boeing</brand>
</plane>
<car>
<name>Second car</name>
<brand>Volvo</brand>
</car>
</xml>
实际上它要复杂得多,XML 大约 500-1000MB 大。我正在使用 quick-xml 阅读这篇文章,它给了我诸如 Start(标签开始)、Text 和 End(标签结束)之类的事件,我正在做一个状态机来跟踪。现在我想将 car 和 plane 的解析卸载到不同的模块(它们需要以不同的方式处理),但共享一个基本实现/特征。
到现在为止还挺好。
现在使用我的状态机,我知道什么时候需要卸载到汽车或飞机上:
</car>
给它.save()
在汽车实现上将其存储在其他地方,并可以释放/销毁实例。 但这意味着在我的主循环中,我需要创建汽车的一个新实例并跟踪它(如果这是主要元素,飞机也是如此。
let mut current_xml_section: I_DONT_KNOW_THE_TYPE = Some()
loop {
match reader.read_event(&mut buf) {
Ok(Event::Start(ref e)) => {
if state == State::Unknown {
match e.name() {
b"car" => {
state = State::InSection;
current_section = CurrentSection::Car;
state_at_depth = depth;
current_xml_section = CurrentSection::Car::new(e); // this won't work
},
b"plane" => {
state = State::InSection;
current_section = CurrentSection::Plane;
state_at_depth = depth;
current_xml_section = CurrentSection::Plane::new(e); // this won't work
},
_ => (),
};
}else{
current_xml_section.start_tag(e); // this won't work
}
depth += 1;
},
Ok(Event::End(ref e)) => {
depth -= 1;
if state == State::InSection && state_at_depth == depth {
state = State::Unknown;
current_section = CurrentSection::Unknown;
state_at_depth = 0;
current_xml_section.save(); // this won't work
// Free current_xml_section here
}else{
if state == State::InSection {
current_xml_section.end_tag(e) // this won't work
}
}
},
// unescape and decode the text event using the reader encoding
Ok(Event::Text(e)) => (
if state == State::InSection {
current_xml_section.text_data(e) // this won't work
}
),
Ok(Event::Eof) => break, // exits the loop when reaching end of file
Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
_ => (), // There are several other `Event`s we do not consider here
}
// if we don't keep a borrow elsewhere, we can clear the buffer to keep memory usage low
buf.clear();
}
}
所以我基本上不知道如何在主循环中保留对“当前”对象的引用(对不起,Python 术语),因为:我也考虑过:
当我尝试解开我的大脑时,任何朝着正确方向的轻推都会受到欢迎!
最佳答案
XML 的事件驱动解析特别适用于范围驱动方法,其中每个级别都由不同的函数解析。
例如,您的主循环可能如下所示:
loop {
match reader.read_event(&mut buf) {
Ok(Event::Start(ref e)) => {
match e.name() {
b"car" => handle_car(&mut reader, &mut buf)?,
b"plane" => handle_plane(&mut reader, &mut buf)?,
_ => return Err("Unexpected Tag"),
}
},
Ok(Event::Eof) => break,
_ => (),
}
}
请注意,内部匹配语句只需要考虑可能出现在顶层的 XML 标记;任何其他标签都是意外的,应该会产生错误。handle_car
看起来像这样:fn handle_car(reader: &mut Reader<&[u8]>, buf:&mut Vec<u8>) -> Result<(),ErrType> {
let mut car = Car::new();
loop {
match reader.read_event(buf) {
Ok(Event::Start(ref e)) => {
match e.name() {
b"name" => {
car.name = handle_name(reader, buf)?;
},
b"brand" => {
car.brand = handle_brand(reader, buf)?;
},
_ => return Err("bad tag"),
}
},
Ok(Event::End(ref e)) => break,
Ok(Event::Eof) => return Err("Unexpected EOF"),
_ => (),
}
}
car.save();
Ok(())
}
handle_car
创建自己的 Car
实例,它位于该函数的范围内。它有自己的循环,它处理其中可能出现的所有标签。如果这些标签包含更多标签,您只需为它们引入一组新的处理函数。该函数返回 Result
因此,如果输入结构与预期不匹配,则可以传递错误(由 quick_xml 产生的任何错误都可以,我已忽略但实际代码会处理)。这种模式在解析 XML 时有一些优势:
<name>
和 <brand>
可以由重复使用的通用函数处理 <car>
可能包含另一个 <car>
),则这由递归处理。 Car
/Plane
完全避免在主循环内。 关于rust - 编写 Rust-y 代码 : Keeping references to different structs depending on type of object in XML,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65908064/