1
0
Fork 0
forked from wry/wry

xml-to-wire: add new utility

This commit is contained in:
Julian Orth 2025-09-20 18:35:49 +02:00
parent aa70bde75d
commit 6d8fb37db4
7 changed files with 829 additions and 11 deletions

28
Cargo.lock generated
View file

@ -106,9 +106,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.95"
version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
name = "arrayref"
@ -1007,18 +1007,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.93"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quick-xml"
version = "0.38.0"
version = "0.38.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8927b0664f5c5a98265138b7e3f90aa19a6b21353182469ace36d4ac527b7b1b"
checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89"
dependencies = [
"memchr",
]
@ -1352,18 +1352,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.11"
version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.11"
version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
dependencies = [
"proc-macro2",
"quote",
@ -1733,6 +1733,14 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "xml-to-wire"
version = "0.1.0"
dependencies = [
"quick-xml",
"thiserror",
]
[[package]]
name = "zerocopy"
version = "0.7.35"

View file

@ -13,7 +13,7 @@ name = "jay"
path = "src/main.rs"
[workspace]
members = ["jay-config", "toml-config", "algorithms", "toml-spec", "wire-to-xml"]
members = ["jay-config", "toml-config", "algorithms", "toml-spec", "wire-to-xml", "xml-to-wire"]
[profile.release]
panic = "abort"

8
xml-to-wire/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "xml-to-wire"
version = "0.1.0"
edition = "2024"
[dependencies]
quick-xml = "0.38.3"
thiserror = "2.0.16"

80
xml-to-wire/src/ast.rs Normal file
View file

@ -0,0 +1,80 @@
pub(crate) struct Protocol {
pub(crate) _name: String,
pub(crate) _copyright: Option<Copyright>,
pub(crate) _description: Option<Description>,
pub(crate) interfaces: Vec<Interface>,
}
pub(crate) struct Copyright {
pub(crate) _body: String,
}
#[derive(Debug)]
pub(crate) struct Description {
pub(crate) _summary: Option<String>,
pub(crate) _body: String,
}
pub(crate) struct Interface {
pub(crate) name: String,
pub(crate) _version: u32,
pub(crate) _description: Option<Description>,
pub(crate) messages: Vec<Message>,
pub(crate) _enums: Vec<Enum>,
}
#[derive(Debug)]
pub(crate) struct Arg {
pub(crate) name: String,
pub(crate) ty: ArgType,
pub(crate) _summary: Option<String>,
pub(crate) _description: Option<Description>,
pub(crate) interface: Option<String>,
pub(crate) allow_null: bool,
pub(crate) enum_: Option<String>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub(crate) enum ArgType {
NewId,
Int,
Uint,
Fixed,
String,
Object,
Array,
Fd,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub(crate) enum MessageType {
Destructor,
}
pub(crate) struct Entry {
pub(crate) _name: String,
pub(crate) _value: String,
pub(crate) _value_u32: u32,
pub(crate) _summary: Option<String>,
pub(crate) _since: Option<u32>,
pub(crate) _deprecated_since: Option<u32>,
pub(crate) _description: Option<Description>,
}
pub(crate) struct Enum {
pub(crate) _name: String,
pub(crate) _since: Option<u32>,
pub(crate) _bitfield: bool,
pub(crate) _description: Option<Description>,
pub(crate) _entries: Vec<Entry>,
}
pub(crate) struct Message {
pub(crate) name: String,
pub(crate) request: bool,
pub(crate) ty: Option<MessageType>,
pub(crate) since: Option<u32>,
pub(crate) _deprecated_since: Option<u32>,
pub(crate) _description: Option<Description>,
pub(crate) args: Vec<Arg>,
}

130
xml-to-wire/src/builder.rs Normal file
View file

@ -0,0 +1,130 @@
use {
crate::{
ast::{ArgType, Message, MessageType},
parser::{ParserError, parse},
},
std::{
fs::File,
io::{self, BufWriter, Write},
mem,
},
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum BuilderError {
#[error("Could not process {0}")]
File(String, #[source] Box<BuilderError>),
#[error("Could not read file")]
ReadFile(#[source] io::Error),
#[error("Could not parse file")]
ParseFile(#[source] ParserError),
#[error("Could not format file")]
FormatFile(#[source] io::Error),
#[error("Could not open {0} for writing")]
OpenFile(String, #[source] io::Error),
}
pub fn handle_file(path: &str) -> Result<(), BuilderError> {
handle_file_(path).map_err(|e| BuilderError::File(path.to_string(), Box::new(e)))
}
fn handle_file_(path: &str) -> Result<(), BuilderError> {
let data = std::fs::read(path).map_err(BuilderError::ReadFile)?;
let protocols = parse(&data).map_err(BuilderError::ParseFile)?;
for protocol in protocols {
for i in protocol.interfaces {
let path = format!("wire/{}.txt", i.name);
let mut file =
BufWriter::new(File::create(&path).map_err(|e| BuilderError::OpenFile(path, e))?);
let mut not_first = false;
let mut handle_msg = |msg: &Message| -> Result<(), io::Error> {
if not_first {
writeln!(file)?;
}
not_first = true;
let ty = match msg.request {
true => "request",
false => "event",
};
write!(file, "{ty} {} ", msg.name)?;
if msg.ty.is_some() || msg.since.is_some() {
write!(file, "(")?;
let mut needs_comma = false;
let handle_comma =
|needs_comma: &mut bool, file: &mut BufWriter<File>| -> io::Result<()> {
if mem::take(needs_comma) {
write!(file, ", ")?;
}
Ok(())
};
if let Some(ty) = msg.ty {
match ty {
MessageType::Destructor => {
handle_comma(&mut needs_comma, &mut file)?;
write!(file, "destructor")?;
needs_comma = true;
}
}
}
if let Some(s) = msg.since {
handle_comma(&mut needs_comma, &mut file)?;
write!(file, "since = {}", s)?;
needs_comma = true;
}
let _ = needs_comma;
write!(file, ") ")?;
}
writeln!(file, "{{")?;
let mut args = msg.args.iter().peekable();
while let Some(arg) = args.next() {
if arg.ty == ArgType::Uint
&& arg.enum_.is_none()
&& let Some(prefix) = arg.name.strip_suffix("_hi")
&& let Some(next) = args.peek()
&& next.ty == ArgType::Uint
&& next.enum_.is_none()
&& next.name.strip_prefix(prefix) == Some("_lo")
{
writeln!(file, " {prefix}: u64,")?;
args.next();
continue;
}
write!(file, " {}: ", arg.name)?;
'ty: {
let ty = match arg.ty {
ArgType::NewId | ArgType::Object => {
write!(
file,
"id({})",
arg.interface.as_deref().unwrap_or("object")
)?;
if arg.ty == ArgType::NewId {
write!(file, " (new)")?;
}
break 'ty;
}
ArgType::Int => "i32",
ArgType::Uint => "u32",
ArgType::Fixed => "fixed",
ArgType::String => match arg.allow_null {
true => "optstr",
false => "str",
},
ArgType::Array => "array(pod(u8))",
ArgType::Fd => "fd",
};
write!(file, "{}", ty)?;
}
writeln!(file, ",")?;
}
writeln!(file, "}}")?;
Ok(())
};
for m in &i.messages {
handle_msg(m).map_err(BuilderError::FormatFile)?;
}
}
}
Ok(())
}

15
xml-to-wire/src/main.rs Normal file
View file

@ -0,0 +1,15 @@
use {
crate::builder::{BuilderError, handle_file},
std::env::args,
};
mod ast;
mod builder;
mod parser;
fn main() -> Result<(), BuilderError> {
for xml in args().skip(1) {
handle_file(&xml)?;
}
Ok(())
}

577
xml-to-wire/src/parser.rs Normal file
View file

@ -0,0 +1,577 @@
use {
crate::ast::{
Arg, ArgType, Copyright, Description, Entry, Enum, Interface, Message, MessageType,
Protocol,
},
quick_xml::{
Reader,
events::{
Event,
attributes::{AttrError, Attribute, Attributes},
},
},
std::{
borrow::Cow,
num::ParseIntError,
str::{FromStr, ParseBoolError},
string::FromUtf8Error,
},
thiserror::Error,
};
#[derive(Debug, Error)]
pub(crate) enum ParserError {
#[error("Could not parse a protocol element")]
Protocol(#[from] ProtocolError),
#[error("Could not read the next event")]
ReadEvent(#[from] quick_xml::Error),
}
#[derive(Debug, Error)]
pub(crate) enum ProtocolError {
#[error("Could not parse an attribute")]
Attribute(#[from] AttributeError),
#[error("Protocol does not have a name")]
MissingName,
#[error("Could not read the next event")]
ReadEvent(#[from] quick_xml::Error),
#[error("Could not the copyright element")]
Copyright(#[from] CopyrightError),
#[error("Could not parse the description element")]
Description(#[from] DescriptionError),
#[error("Could not parse an interface element")]
Interface(#[from] InterfaceError),
}
#[derive(Debug, Error)]
pub(crate) enum CopyrightError {
#[error("Could not read the next event")]
ReadEvent(#[from] quick_xml::Error),
#[error("Could not decode the body as UTF-8")]
DecodeUtf8(#[source] FromUtf8Error),
}
#[derive(Debug, Error)]
pub(crate) enum DescriptionError {
#[error("Could not parse an attribute")]
Attribute(#[from] AttributeError),
#[error("Could not read the next event")]
ReadEvent(#[from] quick_xml::Error),
#[error("Could not decode the body as UTF-8")]
DecodeUtf8(#[source] FromUtf8Error),
}
#[derive(Debug, Error)]
pub(crate) enum InterfaceError {
#[error("Interface has no name")]
MissingName,
#[error("Interface has no version")]
MissingVersion,
#[error("Could not parse an attribute")]
Attribute(#[from] AttributeError),
#[error("Could not read the next event")]
ReadEvent(#[from] quick_xml::Error),
#[error("Could not parse the version")]
Version(#[source] ParseIntError),
#[error("Could not parse a request element")]
Request(#[source] MessageError),
#[error("Could not parse an event element")]
Event(#[source] MessageError),
#[error("Could not parse the description element")]
Description(#[from] DescriptionError),
#[error("Could not parse an enum element")]
Enum(#[from] EnumError),
}
#[derive(Debug, Error)]
pub(crate) enum MessageError {
#[error("Message has no name")]
MissingName,
#[error("Could not parse an attribute")]
Attribute(#[from] AttributeError),
#[error("Could not read the next event")]
ReadEvent(#[from] quick_xml::Error),
#[error("Could not parse the since attribute")]
Since(#[source] ParseIntError),
#[error("Could not parse the deprecated-since attribute")]
DeprecatedSince(#[source] ParseIntError),
#[error("Unknown message type {}", .0)]
UnknownMessageType(String),
#[error("Could not parse an argument element")]
Arg(#[from] ArgError),
#[error("Could not the description element")]
Description(#[from] DescriptionError),
}
#[derive(Debug, Error)]
pub(crate) enum ArgError {
#[error("Argument has no name")]
MissingName,
#[error("Argument has no type")]
MissingType,
#[error("Could not parse an attribute")]
Attribute(#[from] AttributeError),
#[error("Could not read the next event")]
ReadEvent(#[from] quick_xml::Error),
#[error("Could not parse the allow-null attribute")]
AllowNull(#[source] ParseBoolError),
#[error("Unknown arg type {}", .0)]
UnknownArgType(String),
#[error("Could not the description element")]
Description(#[from] DescriptionError),
}
#[derive(Debug, Error)]
pub(crate) enum EnumError {
#[error("Enum has no name")]
MissingName,
#[error("Could not parse an attribute")]
Attribute(#[from] AttributeError),
#[error("Could not read the next event")]
ReadEvent(#[from] quick_xml::Error),
#[error("Could not parse the allow-null attribute")]
AllowNull(#[source] ParseBoolError),
#[error("Could not the description element")]
Description(#[from] DescriptionError),
#[error("Could not parse the since attribute")]
Since(#[source] ParseIntError),
#[error("Could not an entry element")]
Entry(#[from] EntryError),
}
#[derive(Debug, Error)]
pub(crate) enum EntryError {
#[error("Entry has no name")]
MissingName,
#[error("Entry has no value")]
MissingValue,
#[error("Value could not be parsed")]
InvalidValue(#[source] ParseIntError),
#[error("Could not parse an attribute")]
Attribute(#[from] AttributeError),
#[error("Could not read the next event")]
ReadEvent(#[from] quick_xml::Error),
#[error("Could not the description element")]
Description(#[from] DescriptionError),
#[error("Could not parse the since attribute")]
Since(#[source] ParseIntError),
#[error("Could not parse the deprecated-since attribute")]
DeprecatedSince(#[source] ParseIntError),
}
#[derive(Debug, Error)]
pub(crate) enum AttributeError {
#[error("quick_xml returned an error")]
QuickXml(#[from] AttrError),
#[error("Could not decode the value as UTF-8")]
DecodeUtf8(#[from] quick_xml::Error),
}
pub(crate) fn parse(input: &[u8]) -> Result<Vec<Protocol>, ParserError> {
let mut reader = Reader::from_reader(input);
let mut protocols = Vec::new();
loop {
let event = reader.read_event().map_err(ParserError::ReadEvent)?;
let (start, empty) = match event {
Event::Start(s) => (s, false),
Event::Empty(s) => (s, true),
Event::Eof => break,
_ => continue,
};
match start.local_name().as_ref() {
b"protocol" => protocols.push(parse_protocol(&mut reader, start.attributes(), empty)?),
_ => continue,
}
}
Ok(protocols)
}
macro_rules! parse_attr {
($attr:expr) => {
match $attr {
Ok(ref attr) => parse_attr(attr),
Err(e) => return Err(AttributeError::QuickXml(e).into()),
}
};
}
fn parse_attr<'a>(attr: &'a Attribute) -> Result<(&'a [u8], Cow<'a, str>), AttributeError> {
let name = attr.key.local_name().into_inner();
let value = attr.unescape_value().map_err(AttributeError::DecodeUtf8)?;
Ok((name, value))
}
fn parse_protocol(
reader: &mut Reader<&[u8]>,
attributes: Attributes,
empty: bool,
) -> Result<Protocol, ProtocolError> {
let mut name = None;
for attr in attributes {
let (n, value) = parse_attr!(attr)?;
match n {
b"name" => name = Some(value.into_owned()),
_ => continue,
}
}
let mut copyright = None;
let mut description = None;
let mut interfaces = vec![];
if !empty {
loop {
let event = reader.read_event().map_err(ProtocolError::ReadEvent)?;
let (start, empty) = match event {
Event::Start(s) => (s, false),
Event::End(_) => break,
Event::Empty(s) => (s, true),
_ => continue,
};
match start.local_name().as_ref() {
b"copyright" => {
copyright = Some(parse_copyright(reader, start.attributes(), empty)?)
}
b"description" => {
description = Some(parse_description(reader, start.attributes(), empty)?)
}
b"interface" => {
interfaces.push(parse_interface(reader, start.attributes(), empty)?)
}
_ => continue,
}
}
}
Ok(Protocol {
_name: name.ok_or(ProtocolError::MissingName)?,
_copyright: copyright,
_description: description,
interfaces,
})
}
fn parse_copyright(
reader: &mut Reader<&[u8]>,
_attributes: Attributes,
empty: bool,
) -> Result<Copyright, CopyrightError> {
let mut body = Vec::new();
if !empty {
loop {
let event = reader.read_event().map_err(CopyrightError::ReadEvent)?;
match event {
Event::Text(s) => body.extend_from_slice(s.as_ref()),
Event::End(_) => break,
_ => continue,
}
}
}
Ok(Copyright {
_body: String::from_utf8(body).map_err(CopyrightError::DecodeUtf8)?,
})
}
fn parse_description(
reader: &mut Reader<&[u8]>,
attributes: Attributes,
empty: bool,
) -> Result<Description, DescriptionError> {
let mut summary = None;
for attr in attributes {
let (n, value) = parse_attr!(attr)?;
match n {
b"summary" => summary = Some(value.into_owned()),
_ => continue,
}
}
let mut body = Vec::new();
if !empty {
loop {
let event = reader.read_event().map_err(DescriptionError::ReadEvent)?;
match event {
Event::Text(s) => body.extend_from_slice(s.as_ref()),
Event::End(_) => break,
_ => continue,
}
}
}
Ok(Description {
_summary: summary,
_body: String::from_utf8(body).map_err(DescriptionError::DecodeUtf8)?,
})
}
fn parse_interface(
reader: &mut Reader<&[u8]>,
attributes: Attributes,
empty: bool,
) -> Result<Interface, InterfaceError> {
let mut name = None;
let mut version = None;
for attr in attributes {
let (n, value) = parse_attr!(attr)?;
match n {
b"name" => name = Some(value.into_owned()),
b"version" => version = Some(value.parse().map_err(InterfaceError::Version)?),
_ => continue,
}
}
let mut description = None;
let mut messages = Vec::new();
let mut enums = Vec::new();
if !empty {
loop {
let event = reader.read_event().map_err(InterfaceError::ReadEvent)?;
let (start, empty) = match event {
Event::Start(s) => (s, false),
Event::End(_) => break,
Event::Empty(s) => (s, true),
_ => continue,
};
match start.local_name().as_ref() {
b"description" => {
description = Some(parse_description(reader, start.attributes(), empty)?)
}
b"request" => messages.push(
parse_message(reader, start.attributes(), empty, true)
.map_err(InterfaceError::Request)?,
),
b"event" => messages.push(
parse_message(reader, start.attributes(), empty, false)
.map_err(InterfaceError::Event)?,
),
b"enum" => enums.push(parse_enum(reader, start.attributes(), empty)?),
_ => continue,
}
}
}
Ok(Interface {
name: name.ok_or(InterfaceError::MissingName)?,
_version: version.ok_or(InterfaceError::MissingVersion)?,
_description: description,
messages,
_enums: enums,
})
}
fn parse_message(
reader: &mut Reader<&[u8]>,
attributes: Attributes,
empty: bool,
request: bool,
) -> Result<Message, MessageError> {
let mut name = None;
let mut ty = None;
let mut since = None;
let mut deprecated_since = None;
for attr in attributes {
let (n, value) = parse_attr!(attr)?;
match n {
b"name" => name = Some(value.into_owned()),
b"type" => match value.as_ref() {
"destructor" => ty = Some(MessageType::Destructor),
_ => return Err(MessageError::UnknownMessageType(value.into_owned())),
},
b"since" => since = Some(value.parse().map_err(MessageError::Since)?),
b"deprecated-since" => {
deprecated_since = Some(value.parse().map_err(MessageError::DeprecatedSince)?)
}
_ => continue,
}
}
let mut description = None;
let mut args = Vec::new();
if !empty {
loop {
let event = reader.read_event().map_err(MessageError::ReadEvent)?;
let (start, empty) = match event {
Event::Start(s) => (s, false),
Event::End(_) => break,
Event::Empty(s) => (s, true),
_ => continue,
};
match start.local_name().as_ref() {
b"description" => {
description = Some(parse_description(reader, start.attributes(), empty)?)
}
b"arg" => args.push(parse_arg(reader, start.attributes(), empty)?),
_ => continue,
}
}
}
Ok(Message {
name: name.ok_or(MessageError::MissingName)?,
request,
ty,
since,
_deprecated_since: deprecated_since,
_description: description,
args,
})
}
fn parse_arg(
reader: &mut Reader<&[u8]>,
attributes: Attributes,
empty: bool,
) -> Result<Arg, ArgError> {
let mut name = None;
let mut ty = None;
let mut summary = None;
let mut interface = None;
let mut allow_null = None;
let mut enum_ = None;
for attr in attributes {
let (n, value) = parse_attr!(attr)?;
match n {
b"name" => name = Some(value.into_owned()),
b"type" => {
ty = Some(match value.as_ref() {
"int" => ArgType::Int,
"uint" => ArgType::Uint,
"fixed" => ArgType::Fixed,
"string" => ArgType::String,
"array" => ArgType::Array,
"fd" => ArgType::Fd,
"new_id" => ArgType::NewId,
"object" => ArgType::Object,
_ => return Err(ArgError::UnknownArgType(value.into_owned())),
})
}
b"summary" => summary = Some(value.into_owned()),
b"interface" => interface = Some(value.into_owned()),
b"allow-null" => allow_null = Some(value.parse().map_err(ArgError::AllowNull)?),
b"enum" => enum_ = Some(value.into_owned()),
_ => continue,
}
}
let mut description = None;
if !empty {
loop {
let event = reader.read_event().map_err(ArgError::ReadEvent)?;
let (start, empty) = match event {
Event::Start(s) => (s, false),
Event::End(_) => break,
Event::Empty(s) => (s, true),
_ => continue,
};
match start.local_name().as_ref() {
b"description" => {
description = Some(parse_description(reader, start.attributes(), empty)?)
}
_ => continue,
}
}
}
Ok(Arg {
name: name.ok_or(ArgError::MissingName)?,
ty: ty.ok_or(ArgError::MissingType)?,
_summary: summary,
_description: description,
interface,
allow_null: allow_null.unwrap_or_default(),
enum_,
})
}
fn parse_enum(
reader: &mut Reader<&[u8]>,
attributes: Attributes,
empty: bool,
) -> Result<Enum, EnumError> {
let mut name = None;
let mut since = None;
let mut bitfield = None;
for attr in attributes {
let (n, v) = parse_attr!(attr)?;
match n {
b"name" => name = Some(v.into_owned()),
b"since" => since = Some(v.parse().map_err(EnumError::Since)?),
b"bitfield" => bitfield = Some(v.parse().map_err(EnumError::AllowNull)?),
_ => continue,
}
}
let mut description = None;
let mut entries = Vec::new();
if !empty {
loop {
let event = reader.read_event().map_err(EnumError::ReadEvent)?;
let (start, empty) = match event {
Event::Start(s) => (s, false),
Event::End(_) => break,
Event::Empty(s) => (s, true),
_ => continue,
};
match start.local_name().as_ref() {
b"description" => {
description = Some(parse_description(reader, start.attributes(), empty)?)
}
b"entry" => entries.push(parse_entry(reader, start.attributes(), empty)?),
_ => continue,
}
}
}
Ok(Enum {
_name: name.ok_or(EnumError::MissingName)?,
_since: since,
_bitfield: bitfield.unwrap_or_default(),
_description: description,
_entries: entries,
})
}
fn parse_entry(
reader: &mut Reader<&[u8]>,
attributes: Attributes,
empty: bool,
) -> Result<Entry, EntryError> {
let mut name = None;
let mut value = None;
let mut summary = None;
let mut since = None;
let mut deprecated_since = None;
for attr in attributes {
let (n, v) = parse_attr!(attr)?;
match n {
b"name" => name = Some(v.into_owned()),
b"value" => value = Some(v.into_owned()),
b"summary" => summary = Some(v.into_owned()),
b"since" => since = Some(v.parse().map_err(EntryError::Since)?),
b"deprecated-since" => {
deprecated_since = Some(v.parse().map_err(EntryError::DeprecatedSince)?)
}
_ => continue,
}
}
let mut description = None;
if !empty {
loop {
let event = reader.read_event().map_err(EntryError::ReadEvent)?;
let (start, empty) = match event {
Event::Start(s) => (s, false),
Event::End(_) => break,
Event::Empty(s) => (s, true),
_ => continue,
};
match start.local_name().as_ref() {
b"description" => {
description = Some(parse_description(reader, start.attributes(), empty)?)
}
_ => continue,
}
}
}
let value = value.ok_or(EntryError::MissingValue)?;
let value_u32 = if let Some(value) = value.strip_prefix("0x") {
u32::from_str_radix(value, 16).map_err(EntryError::InvalidValue)?
} else {
u32::from_str(&value).map_err(EntryError::InvalidValue)?
};
Ok(Entry {
_name: name.ok_or(EntryError::MissingName)?,
_value: value,
_value_u32: value_u32,
_summary: summary,
_since: since,
_deprecated_since: deprecated_since,
_description: description,
})
}