use crate::XmlVersion;
use crate::encoding::Decoder;
use crate::errors::Result as XmlResult;
use crate::escape::{escape, resolve_predefined_entity};
use crate::name::{LocalName, Namespace, NamespaceResolver, QName};
use crate::utils::{Bytes, is_whitespace};
use std::collections::HashSet;
use std::fmt::{self, Debug, Display, Formatter};
use std::hash::{BuildHasherDefault, DefaultHasher, Hasher};
use std::iter::FusedIterator;
use std::{borrow::Cow, ops::Range};
#[derive(Clone, Eq, PartialEq)]
pub struct Attribute<'a> {
pub key: QName<'a>,
pub value: Cow<'a, [u8]>,
}
impl<'a> Attribute<'a> {
pub fn normalized_value(&self, version: XmlVersion) -> XmlResult<Cow<'a, str>> {
self.normalized_value_with(version, 1, resolve_predefined_entity)
}
pub fn normalized_value_with<'entity>(
&self,
version: XmlVersion,
depth: usize,
resolve_entity: impl FnMut(&str) -> Option<&'entity str>,
) -> XmlResult<Cow<'a, str>> {
use crate::encoding::EncodingError;
use std::str::from_utf8;
let decoded = match &self.value {
Cow::Borrowed(bytes) => Cow::Borrowed(from_utf8(bytes).map_err(EncodingError::Utf8)?),
Cow::Owned(bytes) => {
Cow::Owned(from_utf8(bytes).map_err(EncodingError::Utf8)?.to_owned())
}
};
match version.normalize_attribute_value(&decoded, depth, resolve_entity)? {
Cow::Borrowed(_) => Ok(decoded),
Cow::Owned(s) => Ok(s.into()),
}
}
pub fn decoded_and_normalized_value(
&self,
version: XmlVersion,
decoder: Decoder,
) -> XmlResult<Cow<'a, str>> {
self.decoded_and_normalized_value_with(version, decoder, 1, resolve_predefined_entity)
}
pub fn decoded_and_normalized_value_with<'entity>(
&self,
version: XmlVersion,
decoder: Decoder,
depth: usize,
resolve_entity: impl FnMut(&str) -> Option<&'entity str>,
) -> XmlResult<Cow<'a, str>> {
let decoded = match &self.value {
Cow::Borrowed(bytes) => decoder.decode(bytes)?,
Cow::Owned(bytes) => decoder.decode(bytes)?.into_owned().into(),
};
match version.normalize_attribute_value(&decoded, depth, resolve_entity)? {
Cow::Borrowed(_) => Ok(decoded),
Cow::Owned(s) => Ok(s.into()),
}
}
#[cfg(any(doc, not(feature = "encoding")))]
#[deprecated = "use `Self::normalized_value()`"]
pub fn unescape_value(&self) -> XmlResult<Cow<'a, str>> {
self.normalized_value_with(XmlVersion::Implicit1_0, 1, resolve_predefined_entity)
}
#[cfg(any(doc, not(feature = "encoding")))]
#[deprecated = "use `Self::normalized_value_with()`"]
#[inline]
pub fn unescape_value_with<'entity>(
&self,
resolve_entity: impl FnMut(&str) -> Option<&'entity str>,
) -> XmlResult<Cow<'a, str>> {
self.normalized_value_with(XmlVersion::Implicit1_0, 128, resolve_entity)
}
#[deprecated = "use `Self::decoded_and_normalized_value()`"]
pub fn decode_and_unescape_value(&self, decoder: Decoder) -> XmlResult<Cow<'a, str>> {
self.decoded_and_normalized_value_with(
XmlVersion::Implicit1_0,
decoder,
1,
resolve_predefined_entity,
)
}
#[deprecated = "use `Self::decoded_and_normalized_value_with()`"]
pub fn decode_and_unescape_value_with<'entity>(
&self,
decoder: Decoder,
resolve_entity: impl FnMut(&str) -> Option<&'entity str>,
) -> XmlResult<Cow<'a, str>> {
self.decoded_and_normalized_value_with(
XmlVersion::Implicit1_0,
decoder,
128,
resolve_entity,
)
}
#[inline]
pub fn as_bool(&self) -> Option<bool> {
match self.value.as_ref() {
b"1" | b"true" => Some(true),
b"0" | b"false" => Some(false),
_ => None,
}
}
}
impl<'a> Debug for Attribute<'a> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("Attribute")
.field("key", &Bytes(self.key.as_ref()))
.field("value", &Bytes(&self.value))
.finish()
}
}
impl<'a> From<(&'a [u8], &'a [u8])> for Attribute<'a> {
fn from(val: (&'a [u8], &'a [u8])) -> Attribute<'a> {
Attribute {
key: QName(val.0),
value: Cow::from(val.1),
}
}
}
impl<'a> From<(&'a str, &'a str)> for Attribute<'a> {
fn from(val: (&'a str, &'a str)) -> Attribute<'a> {
Attribute {
key: QName(val.0.as_bytes()),
value: match escape(val.1) {
Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
Cow::Owned(s) => Cow::Owned(s.into_bytes()),
},
}
}
}
impl<'a> From<(&'a str, Cow<'a, str>)> for Attribute<'a> {
fn from(val: (&'a str, Cow<'a, str>)) -> Attribute<'a> {
Attribute {
key: QName(val.0.as_bytes()),
value: match escape(val.1) {
Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
Cow::Owned(s) => Cow::Owned(s.into_bytes()),
},
}
}
}
impl<'a> From<Attr<&'a [u8]>> for Attribute<'a> {
#[inline]
fn from(attr: Attr<&'a [u8]>) -> Self {
Self {
key: attr.key(),
value: Cow::Borrowed(attr.value()),
}
}
}
#[derive(Clone)]
pub struct Attributes<'a> {
bytes: &'a [u8],
state: IterState,
decoder: Decoder,
}
impl<'a> Attributes<'a> {
#[inline]
pub(crate) const fn wrap(buf: &'a [u8], pos: usize, html: bool, decoder: Decoder) -> Self {
Self {
bytes: buf,
state: IterState::new(pos, html),
decoder,
}
}
pub const fn new(buf: &'a str, pos: usize) -> Self {
Self::wrap(buf.as_bytes(), pos, false, Decoder::utf8())
}
pub const fn html(buf: &'a str, pos: usize) -> Self {
Self::wrap(buf.as_bytes(), pos, true, Decoder::utf8())
}
pub fn with_checks(&mut self, val: bool) -> &mut Attributes<'a> {
self.state.check_duplicates = val;
self
}
pub fn has_nil(&mut self, resolver: &NamespaceResolver) -> bool {
use crate::name::ResolveResult::*;
self.any(|attr| {
if let Ok(attr) = attr {
match resolver.resolve_attribute(attr.key) {
(
Bound(Namespace(b"http://www.w3.org/2001/XMLSchema-instance")),
LocalName(b"nil"),
) => attr.as_bool().unwrap_or_default(),
_ => false,
}
} else {
false
}
})
}
#[inline]
pub const fn decoder(&self) -> Decoder {
self.decoder
}
}
impl<'a> Debug for Attributes<'a> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("Attributes")
.field("bytes", &Bytes(self.bytes))
.field("state", &self.state)
.field("decoder", &self.decoder)
.finish()
}
}
impl<'a> Iterator for Attributes<'a> {
type Item = Result<Attribute<'a>, AttrError>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
match self.state.next(self.bytes) {
None => None,
Some(Ok(a)) => Some(Ok(a.map(|range| &self.bytes[range]).into())),
Some(Err(e)) => Some(Err(e)),
}
}
}
impl<'a> FusedIterator for Attributes<'a> {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AttrError {
ExpectedEq(usize),
ExpectedValue(usize),
UnquotedValue(usize),
ExpectedQuote(usize, u8),
Duplicated(usize, usize),
}
impl Display for AttrError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::ExpectedEq(pos) => write!(
f,
r#"position {}: attribute key must be directly followed by `=` or space"#,
pos
),
Self::ExpectedValue(pos) => write!(
f,
r#"position {}: `=` must be followed by an attribute value"#,
pos
),
Self::UnquotedValue(pos) => write!(
f,
r#"position {}: attribute value must be enclosed in `"` or `'`"#,
pos
),
Self::ExpectedQuote(pos, quote) => write!(
f,
r#"position {}: missing closing quote `{}` in attribute value"#,
pos, *quote as char
),
Self::Duplicated(pos1, pos2) => write!(
f,
r#"position {}: duplicated attribute, previous declaration at position {}"#,
pos1, pos2
),
}
}
}
impl std::error::Error for AttrError {}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Attr<T> {
DoubleQ(T, T),
SingleQ(T, T),
Unquoted(T, T),
Empty(T),
}
impl<T> Attr<T> {
#[inline]
pub fn map<U, F>(self, mut f: F) -> Attr<U>
where
F: FnMut(T) -> U,
{
match self {
Attr::DoubleQ(key, value) => Attr::DoubleQ(f(key), f(value)),
Attr::SingleQ(key, value) => Attr::SingleQ(f(key), f(value)),
Attr::Empty(key) => Attr::Empty(f(key)),
Attr::Unquoted(key, value) => Attr::Unquoted(f(key), f(value)),
}
}
}
impl<'a> Attr<&'a [u8]> {
#[inline]
pub const fn key(&self) -> QName<'a> {
QName(match self {
Attr::DoubleQ(key, _) => key,
Attr::SingleQ(key, _) => key,
Attr::Empty(key) => key,
Attr::Unquoted(key, _) => key,
})
}
#[inline]
pub const fn value(&self) -> &'a [u8] {
match self {
Attr::DoubleQ(_, value) => value,
Attr::SingleQ(_, value) => value,
Attr::Empty(_) => &[],
Attr::Unquoted(_, value) => value,
}
}
}
impl<T: AsRef<[u8]>> Debug for Attr<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Attr::DoubleQ(key, value) => f
.debug_tuple("Attr::DoubleQ")
.field(&Bytes(key.as_ref()))
.field(&Bytes(value.as_ref()))
.finish(),
Attr::SingleQ(key, value) => f
.debug_tuple("Attr::SingleQ")
.field(&Bytes(key.as_ref()))
.field(&Bytes(value.as_ref()))
.finish(),
Attr::Empty(key) => f
.debug_tuple("Attr::Empty")
.field(&Bytes(key.as_ref()))
.finish(),
Attr::Unquoted(key, value) => f
.debug_tuple("Attr::Unquoted")
.field(&Bytes(key.as_ref()))
.field(&Bytes(value.as_ref()))
.finish(),
}
}
}
impl<T> From<Attr<T>> for (T, Option<T>) {
#[inline]
fn from(attr: Attr<T>) -> Self {
match attr {
Attr::DoubleQ(key, value) => (key, Some(value)),
Attr::SingleQ(key, value) => (key, Some(value)),
Attr::Empty(key) => (key, None),
Attr::Unquoted(key, value) => (key, Some(value)),
}
}
}
type AttrResult = Result<Attr<Range<usize>>, AttrError>;
#[derive(Clone, Copy, Debug)]
enum State {
Done,
Next(usize),
SkipValue(usize),
SkipEqValue(usize),
}
const SMALL_ATTRIBUTE_COUNT: usize = 32;
#[derive(Default)]
struct IdentityHasher(u64);
impl Hasher for IdentityHasher {
#[inline]
fn finish(&self) -> u64 {
self.0
}
#[inline]
fn write(&mut self, _: &[u8]) {
unreachable!("IdentityHasher only supports u64 keys")
}
#[inline]
fn write_u64(&mut self, n: u64) {
self.0 = n;
}
}
#[inline]
fn hash_name(name: &[u8]) -> u64 {
let mut hasher = DefaultHasher::new();
hasher.write(name);
hasher.finish()
}
#[derive(Clone, Debug)]
pub(crate) struct IterState {
state: State,
html: bool,
check_duplicates: bool,
keys: Vec<Range<usize>>,
key_hashes: Option<HashSet<u64, BuildHasherDefault<IdentityHasher>>>,
}
impl IterState {
pub const fn new(offset: usize, html: bool) -> Self {
Self {
state: State::Next(offset),
html,
check_duplicates: true,
keys: Vec::new(),
key_hashes: None,
}
}
fn recover(&self, slice: &[u8]) -> Option<usize> {
match self.state {
State::Done => None,
State::Next(offset) => Some(offset),
State::SkipValue(offset) => self.skip_value(slice, offset),
State::SkipEqValue(offset) => self.skip_eq_value(slice, offset),
}
}
#[inline]
fn skip_value(&self, slice: &[u8], offset: usize) -> Option<usize> {
let mut iter = (offset..).zip(slice[offset..].iter());
iter.find(|&(_, &b)| is_whitespace(b)).map(|(e, _)| e)
}
#[inline]
fn skip_eq_value(&self, slice: &[u8], offset: usize) -> Option<usize> {
let mut iter = (offset..).zip(slice[offset..].iter());
let quote = match iter.find(|&(_, &b)| !is_whitespace(b)) {
Some((_, b'"')) => b'"',
Some((_, b'\'')) => b'\'',
Some((offset, _)) => return self.skip_value(slice, offset),
None => return None,
};
match iter.find(|&(_, &b)| b == quote) {
Some((e, b'"')) => Some(e),
Some((e, _)) => Some(e),
None => None,
}
}
#[inline]
fn check_for_duplicates(
&mut self,
slice: &[u8],
key: Range<usize>,
) -> Result<Range<usize>, AttrError> {
if self.check_duplicates {
if self.keys.len() >= SMALL_ATTRIBUTE_COUNT {
return self.check_for_duplicates_hashed(slice, key);
}
if let Some(prev) = self
.keys
.iter()
.find(|r| slice[(*r).clone()] == slice[key.clone()])
{
return Err(AttrError::Duplicated(key.start, prev.start));
}
self.keys.push(key.clone());
}
Ok(key)
}
#[cold]
fn check_for_duplicates_hashed(
&mut self,
slice: &[u8],
key: Range<usize>,
) -> Result<Range<usize>, AttrError> {
let keys = &self.keys;
let key_hashes = self.key_hashes.get_or_insert_with(|| {
let mut set = HashSet::with_capacity_and_hasher(
keys.len() * 2,
BuildHasherDefault::<IdentityHasher>::default(),
);
for r in keys {
set.insert(hash_name(&slice[r.clone()]));
}
set
});
if !key_hashes.insert(hash_name(&slice[key.clone()])) {
if let Some(prev) = self
.keys
.iter()
.find(|r| slice[(*r).clone()] == slice[key.clone()])
{
return Err(AttrError::Duplicated(key.start, prev.start));
}
}
self.keys.push(key.clone());
Ok(key)
}
#[inline]
fn key_only(&mut self, slice: &[u8], key: Range<usize>, offset: usize) -> Option<AttrResult> {
Some(if self.html {
self.check_for_duplicates(slice, key).map(Attr::Empty)
} else {
Err(AttrError::ExpectedEq(offset))
})
}
#[inline]
fn double_q(&mut self, key: Range<usize>, value: Range<usize>) -> Option<AttrResult> {
self.state = State::Next(value.end + 1); Some(Ok(Attr::DoubleQ(key, value)))
}
#[inline]
fn single_q(&mut self, key: Range<usize>, value: Range<usize>) -> Option<AttrResult> {
self.state = State::Next(value.end + 1); Some(Ok(Attr::SingleQ(key, value)))
}
pub fn next(&mut self, slice: &[u8]) -> Option<AttrResult> {
let mut iter = match self.recover(slice) {
Some(offset) => (offset..).zip(slice[offset..].iter()),
None => return None,
};
let start_key = match iter.find(|&(_, &b)| !is_whitespace(b)) {
Some((s, _)) => s,
None => {
self.state = State::Done;
return None;
}
};
let (key, offset) = match iter.find(|&(_, &b)| b == b'=' || is_whitespace(b)) {
Some((e, b'=')) => (start_key..e, e),
Some((e, _)) => match iter.find(|&(_, &b)| !is_whitespace(b)) {
Some((offset, b'=')) => (start_key..e, offset),
Some((offset, _)) => {
self.state = State::Next(offset);
return self.key_only(slice, start_key..e, offset);
}
None => {
self.state = State::Done;
return self.key_only(slice, start_key..e, slice.len());
}
},
None => {
self.state = State::Done;
let e = slice.len();
return self.key_only(slice, start_key..e, e);
}
};
let key = match self.check_for_duplicates(slice, key) {
Err(e) => {
self.state = State::SkipEqValue(offset);
return Some(Err(e));
}
Ok(key) => key,
};
let (start_value, quote) = match iter.find(|&(_, &b)| !is_whitespace(b)) {
Some((s, b'"')) => (s + 1, b'"'),
Some((s, b'\'')) => (s + 1, b'\''),
Some((s, _)) if self.html => {
let end = match iter.find(|&(_, &b)| is_whitespace(b)) {
Some((e, _)) => e,
None => slice.len(),
};
self.state = State::Next(end);
return Some(Ok(Attr::Unquoted(key, s..end)));
}
Some((s, _)) => {
self.state = State::SkipValue(s);
return Some(Err(AttrError::UnquotedValue(s)));
}
None => {
self.state = State::Done;
return Some(Err(AttrError::ExpectedValue(slice.len())));
}
};
match iter.find(|&(_, &b)| b == quote) {
Some((e, b'"')) => self.double_q(key, start_value..e),
Some((e, _)) => self.single_q(key, start_value..e),
None => {
self.state = State::Done;
Some(Err(AttrError::ExpectedQuote(slice.len(), quote)))
}
}
}
}
#[cfg(test)]
mod tests;