use std::{borrow::Cow, collections::HashSet};
use winnow::{
LocatingSlice, ModalResult, Parser,
combinator::{alt, cut_err, delimited, eof, opt, preceded, repeat},
error::{ErrMode, ParserError},
stream::{Location, Stateful},
token::{any, take_till, take_until, take_while},
};
use zbus_names::{InterfaceName, MemberName, PropertyName};
use crate::{
Annotation, Arg, ArgDirection, Interface, Method, Node, Property, PropertyAccess, Signal,
Signature, Warning,
error::{Error, Result, XmlError},
telepathy::{self, TypeDef},
};
const MAX_DEPTH: usize = 1024;
pub(crate) fn parse<'a>(document: &str) -> Result<Node<'a>> {
parse_with_warnings(document).map(|(node, _)| node)
}
pub(crate) fn parse_with_warnings<'a>(document: &str) -> Result<(Node<'a>, Vec<Warning>)> {
let mut input = Input {
input: LocatingSlice::new(document),
state: State {
document,
warnings: Vec::new(),
},
};
match document_node(&mut input) {
Ok(node) => Ok((node, input.state.warnings)),
Err(ErrMode::Backtrack(error) | ErrMode::Cut(error)) => Err(error.into_error()),
Err(ErrMode::Incomplete(_)) => Err(Error::Xml(XmlError::new(
"unexpected end of document",
document.len(),
))),
}
}
fn document_node<'i>(input: &mut Input<'i>) -> PResult<Node<'static>> {
ignorable(input)?;
if opt(eof).parse_next(input)?.is_some() {
return Err(error("missing root element", input));
}
let (tag, attrs, self_closing) = start_element(input)?;
node(input, tag, attrs, self_closing)
}
type Input<'i> = Stateful<LocatingSlice<&'i str>, State<'i>>;
#[derive(Debug)]
struct State<'i> {
document: &'i str,
warnings: Vec<Warning>,
}
fn warn(input: &mut Input<'_>, warning: Warning) {
input.state.warnings.push(warning);
}
type PResult<O> = ModalResult<O, ParseError>;
#[derive(Debug)]
enum ParseError {
Xml {
message: Cow<'static, str>,
offset: usize,
},
Domain(Error),
}
impl ParseError {
fn xml(message: impl Into<Cow<'static, str>>, offset: usize) -> ErrMode<Self> {
ErrMode::Cut(ParseError::Xml {
message: message.into(),
offset,
})
}
fn domain(error: Error) -> ErrMode<Self> {
ErrMode::Cut(ParseError::Domain(error))
}
fn into_error(self) -> Error {
match self {
ParseError::Xml { message, offset } => Error::Xml(XmlError::new(message, offset)),
ParseError::Domain(error) => error,
}
}
}
impl<'i> ParserError<Input<'i>> for ParseError {
type Inner = Self;
fn from_input(input: &Input<'i>) -> Self {
ParseError::Xml {
message: Cow::Borrowed("malformed markup"),
offset: input.current_token_start(),
}
}
fn into_inner(self) -> std::result::Result<Self::Inner, Self> {
Ok(self)
}
}
fn error(message: impl Into<Cow<'static, str>>, input: &Input<'_>) -> ErrMode<ParseError> {
ParseError::xml(message, input.current_token_start())
}
fn node<'i>(
input: &mut Input<'i>,
tag: &'i str,
attrs: Attrs<'i>,
self_closing: bool,
) -> PResult<Node<'static>> {
let root = empty_node(&attrs);
if self_closing {
return Ok(root);
}
let mut open: Vec<(&'i str, Node<'static>)> = vec![(tag, root)];
loop {
ignorable(input)?;
let tag = open.last().expect("the root is popped only by returning").0;
if opt(eof).parse_next(input)?.is_some() {
return Err(error(format!("missing `</{tag}>`"), input));
}
if let Some(close) = opt(closing_tag).parse_next(input)? {
if close != tag {
return Err(error(
format!("unexpected `</{close}>` while parsing `<{tag}>`"),
input,
));
}
let (_, finished) = open.pop().expect("a close tag was just matched");
match open.last_mut() {
Some((_, parent)) => parent.nodes.push(finished),
None => return Ok(finished),
}
continue;
}
let (child, child_attrs, child_self_closing) = start_element(input)?;
match child {
"interface" => {
let interface = interface(input, child, child_attrs, child_self_closing)?;
open.last_mut()
.expect("non-empty")
.1
.interfaces
.push(interface);
}
"node" if child_self_closing => {
open.last_mut()
.expect("non-empty")
.1
.nodes
.push(empty_node(&child_attrs));
}
"node" => {
if open.len() >= MAX_DEPTH {
return Err(error("maximum element nesting depth exceeded", input));
}
open.push((child, empty_node(&child_attrs)));
}
other if is_docstring(other) => {
let docstring = capture_docstring(input, other, child_self_closing)?;
let node = &mut open.last_mut().expect("non-empty").1;
node.docstring = docstring.or(node.docstring.take());
}
other => {
if let Some(def) =
telepathy_type_def(input, other, &child_attrs, child_self_closing)?
{
open.last_mut()
.expect("non-empty")
.1
.telepathy_types
.push(def);
}
}
}
}
}
fn empty_node(attrs: &Attrs<'_>) -> Node<'static> {
Node {
name: attrs.optional("name").map(str::to_owned),
interfaces: Vec::new(),
nodes: Vec::new(),
docstring: None,
telepathy_types: Vec::new(),
}
}
fn interface<'i>(
input: &mut Input<'i>,
tag: &'i str,
attrs: Attrs<'i>,
self_closing: bool,
) -> PResult<Interface<'static>> {
let name = attrs.name(|n| InterfaceName::try_from(n).map_err(Error::Name))?;
let mut methods = Vec::new();
let mut properties = Vec::new();
let mut signals = Vec::new();
let mut annotations = Vec::new();
let mut docstring = None;
let mut telepathy_types = Vec::new();
children(
input,
tag,
self_closing,
|input, child, attrs, sc| match child {
"method" => {
methods.push(method(input, child, attrs, sc)?);
Ok(true)
}
"property" => {
properties.push(property(input, child, attrs, sc)?);
Ok(true)
}
"signal" => {
signals.push(signal(input, child, attrs, sc)?);
Ok(true)
}
"annotation" => {
annotations.push(annotation(input, child, attrs, sc)?);
Ok(true)
}
other if is_docstring(other) => {
docstring = capture_docstring(input, other, sc)?.or(docstring.take());
Ok(true)
}
other => {
if let Some(def) = telepathy_type_def(input, other, &attrs, sc)? {
telepathy_types.push(def);
}
Ok(true)
}
},
)?;
Ok(Interface {
name,
methods,
properties,
signals,
annotations,
docstring,
telepathy_types,
})
}
fn method<'i>(
input: &mut Input<'i>,
tag: &'i str,
attrs: Attrs<'i>,
self_closing: bool,
) -> PResult<Method<'static>> {
let name = attrs.name(|n| MemberName::try_from(n).map_err(Error::Name))?;
let mut args = Vec::new();
let mut annotations = Vec::new();
let mut docstring = None;
children(
input,
tag,
self_closing,
|input, child, attrs, sc| match child {
"arg" => {
args.push(arg(input, child, attrs, sc)?);
Ok(true)
}
"annotation" => {
annotations.push(annotation(input, child, attrs, sc)?);
Ok(true)
}
other => docstring_or_skip(input, other, &attrs, sc, &mut docstring),
},
)?;
Ok(Method {
name,
args,
annotations,
docstring,
})
}
fn signal<'i>(
input: &mut Input<'i>,
tag: &'i str,
attrs: Attrs<'i>,
self_closing: bool,
) -> PResult<Signal<'static>> {
let name = attrs.name(|n| MemberName::try_from(n).map_err(Error::Name))?;
let mut args = Vec::new();
let mut annotations = Vec::new();
let mut docstring = None;
children(
input,
tag,
self_closing,
|input, child, attrs, sc| match child {
"arg" => {
args.push(arg(input, child, attrs, sc)?);
Ok(true)
}
"annotation" => {
annotations.push(annotation(input, child, attrs, sc)?);
Ok(true)
}
other => docstring_or_skip(input, other, &attrs, sc, &mut docstring),
},
)?;
Ok(Signal {
name,
args,
annotations,
docstring,
})
}
fn property<'i>(
input: &mut Input<'i>,
tag: &'i str,
attrs: Attrs<'i>,
self_closing: bool,
) -> PResult<Property<'static>> {
let name = attrs.name(|n| PropertyName::try_from(n).map_err(Error::Name))?;
let ty = attrs.signature()?;
let access = match attrs.required("access")? {
"read" => PropertyAccess::Read,
"write" => PropertyAccess::Write,
"readwrite" => PropertyAccess::ReadWrite,
other => return Err(error(format!("invalid property access `{other}`"), input)),
};
let tp_type = attrs.tp_type().map(str::to_owned);
let mut annotations = Vec::new();
let mut docstring = None;
children(
input,
tag,
self_closing,
|input, child, attrs, sc| match child {
"annotation" => {
annotations.push(annotation(input, child, attrs, sc)?);
Ok(true)
}
other => docstring_or_skip(input, other, &attrs, sc, &mut docstring),
},
)?;
Ok(Property {
name,
ty,
access,
annotations,
docstring,
tp_type,
})
}
fn arg<'i>(
input: &mut Input<'i>,
tag: &'i str,
attrs: Attrs<'i>,
self_closing: bool,
) -> PResult<Arg> {
let name = attrs.optional("name").map(str::to_owned);
let ty = attrs.signature()?;
let direction = match attrs.optional("direction") {
Some("in") => Some(ArgDirection::In),
Some("out") => Some(ArgDirection::Out),
Some(other) => {
return Err(error(
format!("invalid argument direction `{other}`"),
input,
));
}
None => None,
};
let tp_type = attrs.tp_type().map(str::to_owned);
let mut annotations = Vec::new();
let mut docstring = None;
children(
input,
tag,
self_closing,
|input, child, attrs, sc| match child {
"annotation" => {
annotations.push(annotation(input, child, attrs, sc)?);
Ok(true)
}
other => docstring_or_skip(input, other, &attrs, sc, &mut docstring),
},
)?;
Ok(Arg {
name,
ty,
direction,
annotations,
docstring,
tp_type,
})
}
fn annotation<'i>(
input: &mut Input<'i>,
tag: &'i str,
attrs: Attrs<'i>,
self_closing: bool,
) -> PResult<Annotation> {
let name = attrs.required("name")?.to_owned();
let value = attrs.required("value")?.to_owned();
children(input, tag, self_closing, |input, child, attrs, sc| {
skip_unsupported(input, child, &attrs, sc)?;
Ok(true)
})?;
Ok(Annotation { name, value })
}
fn children<'i>(
input: &mut Input<'i>,
tag: &'i str,
self_closing: bool,
mut handle: impl FnMut(&mut Input<'i>, &'i str, Attrs<'i>, bool) -> PResult<bool>,
) -> PResult<()> {
if self_closing {
return Ok(());
}
loop {
ignorable(input)?;
if opt(eof).parse_next(input)?.is_some() {
return Err(error(format!("missing `</{tag}>`"), input));
}
if let Some(close) = opt(closing_tag).parse_next(input)? {
if close != tag {
return Err(error(
format!("unexpected `</{close}>` while parsing `<{tag}>`"),
input,
));
}
return Ok(());
}
let (child, attrs, self_closing) = start_element(input)?;
if !handle(input, child, attrs, self_closing)? {
skip_element(input, child, self_closing)?;
}
}
}
fn skip_element<'i>(input: &mut Input<'i>, tag: &'i str, self_closing: bool) -> PResult<()> {
if self_closing {
return Ok(());
}
let mut open = vec![tag];
while let Some(&expected) = open.last() {
ignorable(input)?;
if opt(eof).parse_next(input)?.is_some() {
return Err(error(format!("missing `</{expected}>`"), input));
}
if let Some(close) = opt(closing_tag).parse_next(input)? {
if close != expected {
return Err(error(
format!("unexpected `</{close}>` while parsing `<{expected}>`"),
input,
));
}
open.pop();
continue;
}
let (child, _, self_closing) = start_element(input)?;
if !self_closing {
if open.len() >= MAX_DEPTH {
return Err(error("maximum element nesting depth exceeded", input));
}
open.push(child);
}
}
Ok(())
}
fn skip_unsupported<'i>(
input: &mut Input<'i>,
tag: &'i str,
attrs: &Attrs<'i>,
self_closing: bool,
) -> PResult<()> {
warn(input, Warning::unsupported(tag, attrs.offset - 1));
skip_element(input, tag, self_closing)
}
fn docstring_or_skip<'i>(
input: &mut Input<'i>,
child: &'i str,
attrs: &Attrs<'i>,
self_closing: bool,
slot: &mut Option<String>,
) -> PResult<bool> {
if is_docstring(child) {
*slot = capture_docstring(input, child, self_closing)?.or(slot.take());
} else {
skip_unsupported(input, child, attrs, self_closing)?;
}
Ok(true)
}
fn capture_docstring<'i>(
input: &mut Input<'i>,
tag: &'i str,
self_closing: bool,
) -> PResult<Option<String>> {
if self_closing {
return Ok(None);
}
let start = input.current_token_start();
let end = skip_to_close(input, tag)?;
let content = input.state.document[start..end].trim();
Ok((!content.is_empty()).then(|| content.to_owned()))
}
fn skip_to_close<'i>(input: &mut Input<'i>, tag: &'i str) -> PResult<usize> {
let mut open = vec![tag];
loop {
ignorable(input)?;
let here = input.current_token_start();
let expected = *open.last().expect("non-empty until returning");
if opt(eof).parse_next(input)?.is_some() {
return Err(error(format!("missing `</{expected}>`"), input));
}
if let Some(close) = opt(closing_tag).parse_next(input)? {
if close != expected {
return Err(error(
format!("unexpected `</{close}>` while parsing `<{expected}>`"),
input,
));
}
open.pop();
if open.is_empty() {
return Ok(here);
}
continue;
}
let (child, _, self_closing) = start_element(input)?;
if !self_closing {
if open.len() >= MAX_DEPTH {
return Err(error("maximum element nesting depth exceeded", input));
}
open.push(child);
}
}
}
fn local_name(name: &str) -> &str {
name.rsplit(':').next().unwrap_or(name)
}
fn is_docstring(name: &str) -> bool {
local_name(name) == "docstring"
}
fn is_tp_type(name: &str) -> bool {
name.split_once(':')
.is_some_and(|(prefix, local)| !prefix.is_empty() && local == "type")
}
enum SignatureAttr {
Missing,
Invalid,
Value(Signature),
}
impl SignatureAttr {
fn parse(value: Option<&str>) -> Self {
match value {
None => SignatureAttr::Missing,
Some(value) => match zvariant::Signature::try_from(value.as_bytes()) {
Ok(zvariant::Signature::Unit) | Err(_) => SignatureAttr::Invalid,
Ok(signature) => SignatureAttr::Value(Signature(signature)),
},
}
}
}
fn telepathy_type_def<'i>(
input: &mut Input<'i>,
tag: &'i str,
attrs: &Attrs<'i>,
self_closing: bool,
) -> PResult<Option<TypeDef>> {
match local_name(tag) {
"simple-type" => simple_type(input, tag, attrs, self_closing),
"enum" => enum_def(input, tag, attrs, self_closing),
"struct" => struct_def(input, tag, attrs, self_closing),
"mapping" => mapping_def(input, tag, attrs, self_closing),
_ => {
skip_unsupported(input, tag, attrs, self_closing)?;
Ok(None)
}
}
}
fn malformed<T>(
input: &mut Input<'_>,
tag: &str,
position: usize,
reason: impl std::fmt::Display,
) -> PResult<Option<T>> {
warn(input, Warning::malformed(tag, position, reason));
Ok(None)
}
fn simple_type<'i>(
input: &mut Input<'i>,
tag: &'i str,
attrs: &Attrs<'i>,
self_closing: bool,
) -> PResult<Option<TypeDef>> {
let position = attrs.offset - 1;
let name = attrs.optional("name").map(str::to_owned);
let ty = SignatureAttr::parse(attrs.optional("type"));
let mut docstring = None;
children(input, tag, self_closing, |input, child, attrs, sc| {
docstring_or_skip(input, child, &attrs, sc, &mut docstring)
})?;
let Some(name) = name else {
return malformed(input, tag, position, "missing attribute `name`");
};
let ty = match ty {
SignatureAttr::Value(ty) => ty,
SignatureAttr::Missing => {
return malformed(input, tag, position, "missing attribute `type`");
}
SignatureAttr::Invalid => {
return malformed(input, tag, position, "invalid signature in `type`");
}
};
Ok(Some(TypeDef::SimpleType(telepathy::SimpleType {
name,
ty,
docstring,
})))
}
fn enum_def<'i>(
input: &mut Input<'i>,
tag: &'i str,
attrs: &Attrs<'i>,
self_closing: bool,
) -> PResult<Option<TypeDef>> {
let position = attrs.offset - 1;
let name = attrs.optional("name").map(str::to_owned);
let ty = SignatureAttr::parse(attrs.optional("type"));
let mut values = Vec::new();
let mut incomplete_value = false;
let mut docstring = None;
children(input, tag, self_closing, |input, child, attrs, sc| {
if local_name(child) == "enumvalue" {
match enum_value(input, child, &attrs, sc)? {
Some(value) => values.push(value),
None => incomplete_value = true,
}
Ok(true)
} else {
docstring_or_skip(input, child, &attrs, sc, &mut docstring)
}
})?;
let Some(name) = name else {
return malformed(input, tag, position, "missing attribute `name`");
};
let ty = match ty {
SignatureAttr::Value(ty) => ty,
SignatureAttr::Missing => {
return malformed(input, tag, position, "missing attribute `type`");
}
SignatureAttr::Invalid => {
return malformed(input, tag, position, "invalid signature in `type`");
}
};
if incomplete_value {
let reason = "an enumvalue is missing its `suffix` or `value` attribute";
return malformed(input, tag, position, reason);
}
Ok(Some(TypeDef::Enum(telepathy::Enum {
name,
ty,
values,
docstring,
})))
}
fn enum_value<'i>(
input: &mut Input<'i>,
tag: &'i str,
attrs: &Attrs<'i>,
self_closing: bool,
) -> PResult<Option<telepathy::EnumValue>> {
let suffix = attrs.optional("suffix").map(str::to_owned);
let value = attrs.optional("value").map(str::to_owned);
let mut docstring = None;
children(input, tag, self_closing, |input, child, attrs, sc| {
docstring_or_skip(input, child, &attrs, sc, &mut docstring)
})?;
Ok(suffix
.zip(value)
.map(|(suffix, value)| telepathy::EnumValue {
suffix,
value,
docstring,
}))
}
fn struct_def<'i>(
input: &mut Input<'i>,
tag: &'i str,
attrs: &Attrs<'i>,
self_closing: bool,
) -> PResult<Option<TypeDef>> {
let position = attrs.offset - 1;
let name = attrs.optional("name").map(str::to_owned);
let (members, incomplete_member, docstring) = members(input, tag, self_closing)?;
let Some(name) = name else {
return malformed(input, tag, position, "missing attribute `name`");
};
if incomplete_member {
return malformed(input, tag, position, INCOMPLETE_MEMBER);
}
if members.is_empty() {
return malformed(input, tag, position, "no members");
}
Ok(Some(TypeDef::Struct(telepathy::Struct {
name,
members,
docstring,
})))
}
fn mapping_def<'i>(
input: &mut Input<'i>,
tag: &'i str,
attrs: &Attrs<'i>,
self_closing: bool,
) -> PResult<Option<TypeDef>> {
let position = attrs.offset - 1;
let name = attrs.optional("name").map(str::to_owned);
let (members, incomplete_member, docstring) = members(input, tag, self_closing)?;
let Some(name) = name else {
return malformed(input, tag, position, "missing attribute `name`");
};
if incomplete_member {
return malformed(input, tag, position, INCOMPLETE_MEMBER);
}
let mut members = members.into_iter();
let (key, value) = match (members.next(), members.next(), members.next()) {
(Some(key), Some(value), None) => (key, value),
_ => return malformed(input, tag, position, "expected exactly 2 members"),
};
if !telepathy::is_basic(key.ty()) {
return malformed(input, tag, position, "the key is not a basic type");
}
Ok(Some(TypeDef::Mapping(telepathy::Mapping {
name,
key,
value,
docstring,
})))
}
const INCOMPLETE_MEMBER: &str =
"a member is missing its `name` or `type` attribute, or has an invalid signature";
#[allow(clippy::type_complexity)]
fn members<'i>(
input: &mut Input<'i>,
tag: &'i str,
self_closing: bool,
) -> PResult<(Vec<telepathy::Member>, bool, Option<String>)> {
let mut members = Vec::new();
let mut incomplete_member = false;
let mut docstring = None;
children(input, tag, self_closing, |input, child, attrs, sc| {
if local_name(child) == "member" {
match member(input, child, &attrs, sc)? {
Some(member) => members.push(member),
None => incomplete_member = true,
}
Ok(true)
} else {
docstring_or_skip(input, child, &attrs, sc, &mut docstring)
}
})?;
Ok((members, incomplete_member, docstring))
}
fn member<'i>(
input: &mut Input<'i>,
tag: &'i str,
attrs: &Attrs<'i>,
self_closing: bool,
) -> PResult<Option<telepathy::Member>> {
let name = attrs.optional("name").map(str::to_owned);
let ty = SignatureAttr::parse(attrs.optional("type"));
let tp_type = attrs.tp_type().map(str::to_owned);
let mut docstring = None;
children(input, tag, self_closing, |input, child, attrs, sc| {
docstring_or_skip(input, child, &attrs, sc, &mut docstring)
})?;
let (Some(name), SignatureAttr::Value(ty)) = (name, ty) else {
return Ok(None);
};
Ok(Some(telepathy::Member {
name,
ty,
tp_type,
docstring,
}))
}
fn ignorable<'i>(input: &mut Input<'i>) -> PResult<()> {
repeat(
0..,
alt((
comment,
cdata,
processing_instruction,
markup_declaration,
text,
)),
)
.parse_next(input)
}
fn comment<'i>(input: &mut Input<'i>) -> PResult<()> {
("<!--", cut_err((take_until(0.., "-->"), "-->")))
.void()
.parse_next(input)
}
fn cdata<'i>(input: &mut Input<'i>) -> PResult<()> {
("<![CDATA[", cut_err((take_until(0.., "]]>"), "]]>")))
.void()
.parse_next(input)
}
fn processing_instruction<'i>(input: &mut Input<'i>) -> PResult<()> {
("<?", cut_err((take_until(0.., "?>"), "?>")))
.void()
.parse_next(input)
}
fn markup_declaration<'i>(input: &mut Input<'i>) -> PResult<()> {
preceded("<!", cut_err(markup_body)).parse_next(input)
}
fn markup_body<'i>(input: &mut Input<'i>) -> PResult<()> {
let mut bracket_depth = 0usize;
loop {
match any.parse_next(input)? {
quote @ ('"' | '\'') => {
(take_until(0.., quote), any).void().parse_next(input)?;
}
'[' => bracket_depth += 1,
']' => bracket_depth = bracket_depth.saturating_sub(1),
'>' if bracket_depth == 0 => return Ok(()),
_ => (),
}
}
}
fn text<'i>(input: &mut Input<'i>) -> PResult<()> {
take_till(1.., '<').void().parse_next(input)
}
fn closing_tag<'i>(input: &mut Input<'i>) -> PResult<&'i str> {
delimited("</", xml_name, (whitespace, '>')).parse_next(input)
}
fn start_element<'i>(input: &mut Input<'i>) -> PResult<(&'i str, Attrs<'i>, bool)> {
let (name, span) = preceded('<', xml_name.with_span()).parse_next(input)?;
let pairs = attributes(input)?;
let self_closing =
preceded(whitespace, alt(("/>".value(true), ">".value(false)))).parse_next(input)?;
Ok((
name,
Attrs {
element: name,
offset: span.start,
pairs,
},
self_closing,
))
}
struct Attrs<'i> {
element: &'i str,
offset: usize,
pairs: Vec<(&'i str, Cow<'i, str>)>,
}
impl<'i> Attrs<'i> {
fn optional(&self, key: &str) -> Option<&str> {
self.pairs
.iter()
.find(|(name, _)| *name == key)
.map(|(_, value)| value.as_ref())
}
fn required(&self, key: &str) -> PResult<&str> {
self.optional(key).ok_or_else(|| {
ParseError::xml(
format!("missing attribute `{key}` on `<{}>`", self.element),
self.offset,
)
})
}
fn name<T>(&self, parse: impl FnOnce(String) -> std::result::Result<T, Error>) -> PResult<T> {
parse(self.required("name")?.to_owned()).map_err(ParseError::domain)
}
fn signature(&self) -> PResult<Signature> {
zvariant::Signature::try_from(self.required("type")?.as_bytes())
.map(Signature)
.map_err(|e| ParseError::domain(zvariant::Error::from(e).into()))
}
fn tp_type(&self) -> Option<&str> {
self.pairs
.iter()
.find(|(name, _)| is_tp_type(name))
.map(|(_, value)| value.as_ref())
}
}
fn attributes<'i>(input: &mut Input<'i>) -> PResult<Vec<(&'i str, Cow<'i, str>)>> {
let raw: Vec<Attribute<'i>> = repeat(0.., attribute).parse_next(input)?;
let mut pairs: Vec<(&'i str, Cow<'i, str>)> = Vec::with_capacity(raw.len());
let mut seen: Option<HashSet<&'i str>> = None;
for attr in raw {
let duplicate = match seen {
Some(ref mut seen) => !seen.insert(attr.name),
None => {
let duplicate = pairs.iter().any(|(name, _)| *name == attr.name);
if !duplicate && pairs.len() >= 32 {
let mut set: HashSet<&'i str> = pairs.iter().map(|(name, _)| *name).collect();
set.insert(attr.name);
seen = Some(set);
}
duplicate
}
};
if duplicate {
return Err(ParseError::xml(
format!("duplicate attribute `{}`", attr.name),
attr.name_offset,
));
}
let value = unescape(attr.value)
.map_err(|(message, at)| ParseError::xml(message, attr.value_offset + at))?;
pairs.push((attr.name, value));
}
Ok(pairs)
}
struct Attribute<'i> {
name: &'i str,
name_offset: usize,
value: &'i str,
value_offset: usize,
}
fn attribute<'i>(input: &mut Input<'i>) -> PResult<Attribute<'i>> {
let (name, name_span) = preceded(whitespace1, xml_name.with_span()).parse_next(input)?;
(whitespace, '=', whitespace).parse_next(input)?;
let (value, value_offset) = quoted_value(input)?;
Ok(Attribute {
name,
name_offset: name_span.start,
value,
value_offset,
})
}
fn xml_name<'i>(input: &mut Input<'i>) -> PResult<&'i str> {
take_while(1.., |c: char| {
!c.is_ascii_whitespace() && !matches!(c, '=' | '/' | '>' | '<')
})
.parse_next(input)
}
fn quoted_value<'i>(input: &mut Input<'i>) -> PResult<(&'i str, usize)> {
alt((
delimited('"', take_until(0.., '"').with_span(), '"'),
delimited('\'', take_until(0.., '\'').with_span(), '\''),
))
.map(|(value, span)| (value, span.start))
.parse_next(input)
}
fn whitespace<'i>(input: &mut Input<'i>) -> PResult<&'i str> {
take_while(0.., |c: char| c.is_ascii_whitespace()).parse_next(input)
}
fn whitespace1<'i>(input: &mut Input<'i>) -> PResult<&'i str> {
take_while(1.., |c: char| c.is_ascii_whitespace()).parse_next(input)
}
fn unescape(value: &str) -> std::result::Result<Cow<'_, str>, (String, usize)> {
if !value.contains(['&', '\t', '\n', '\r']) {
return Ok(Cow::Borrowed(value));
}
let mut unescaped = String::with_capacity(value.len());
let mut rest = value;
while let Some(i) = rest.find(['&', '\t', '\n', '\r']) {
unescaped.push_str(&rest[..i]);
let reference = value.len() - rest.len() + i;
if rest.as_bytes()[i] != b'&' {
unescaped.push(' ');
let width = if rest.as_bytes()[i] == b'\r' && rest.as_bytes().get(i + 1) == Some(&b'\n')
{
2
} else {
1
};
rest = &rest[i + width..];
continue;
}
rest = &rest[i + 1..];
let end = rest
.find(';')
.ok_or(("unterminated entity reference".to_string(), reference))?;
let entity = &rest[..end];
match entity {
"amp" => unescaped.push('&'),
"lt" => unescaped.push('<'),
"gt" => unescaped.push('>'),
"quot" => unescaped.push('"'),
"apos" => unescaped.push('\''),
_ => unescaped.push(char_reference(entity).map_err(|e| (e, reference))?),
}
rest = &rest[end + 1..];
}
unescaped.push_str(rest);
Ok(Cow::Owned(unescaped))
}
fn char_reference(entity: &str) -> std::result::Result<char, String> {
let invalid = || format!("invalid character reference `&{entity};`");
let code = if let Some(hex) = entity
.strip_prefix("#x")
.or_else(|| entity.strip_prefix("#X"))
{
if hex.is_empty() || !hex.bytes().all(|b| b.is_ascii_hexdigit()) {
return Err(invalid());
}
u32::from_str_radix(hex, 16).ok()
} else if let Some(dec) = entity.strip_prefix('#') {
if dec.is_empty() || !dec.bytes().all(|b| b.is_ascii_digit()) {
return Err(invalid());
}
dec.parse().ok()
} else {
return Err(format!("unknown entity `&{entity};`"));
};
code.and_then(char::from_u32)
.filter(|c| is_xml_char(*c))
.ok_or_else(invalid)
}
fn is_xml_char(c: char) -> bool {
matches!(
c,
'\u{9}' | '\u{A}' | '\u{D}' | '\u{20}'..='\u{D7FF}' | '\u{E000}'..='\u{FFFD}' | '\u{10000}'..='\u{10FFFF}'
)
}
pub(crate) fn escape(value: &str) -> Cow<'_, str> {
if !value.contains(['&', '<', '>', '"', '\'', '\t', '\n', '\r']) {
return Cow::Borrowed(value);
}
let mut escaped = String::with_capacity(value.len() + 8);
for c in value.chars() {
match c {
'&' => escaped.push_str("&"),
'<' => escaped.push_str("<"),
'>' => escaped.push_str(">"),
'"' => escaped.push_str("""),
'\'' => escaped.push_str("'"),
'\t' => escaped.push_str("	"),
'\n' => escaped.push_str(" "),
'\r' => escaped.push_str(" "),
c => escaped.push(c),
}
}
Cow::Owned(escaped)
}