use std::fmt::{self, Debug, Display, Formatter}; use std::iter::FusedIterator; use std::{borrow::Cow, ops::Range};
/// A struct representing a key/value XML attribute. /// /// Field `value` stores raw bytes, possibly containing escape-sequences. Most users will likely /// want to access the value using one of the [`unescape_value`] and [`decode_and_unescape_value`] /// functions. /// /// [`unescape_value`]: Self::unescape_value /// [`decode_and_unescape_value`]: Self::decode_and_unescape_value #[derive(Clone, Eq, PartialEq)] pubstruct Attribute<'a> { /// The key to uniquely define the attribute. /// /// If [`Attributes::with_checks`] is turned off, the key might not be unique. pub key: QName<'a>, /// The raw value of the attribute. pub value: Cow<'a, [u8]>,
}
impl<'a> Attribute<'a> { /// Decodes using UTF-8 then unescapes the value. /// /// This is normally the value you are interested in. Escape sequences such as `>` are /// replaced with their unescaped equivalents such as `>`. /// /// This will allocate if the value contains any escape sequences. /// /// See also [`unescape_value_with()`](Self::unescape_value_with) /// /// This method is available only if [`encoding`] feature is **not** enabled. /// /// [`encoding`]: ../../index.html#encoding #[cfg(any(doc, not(feature = "encoding")))] pubfn unescape_value(&self) -> XmlResult<Cow<'a, str>> { self.unescape_value_with(resolve_predefined_entity)
}
/// Decodes using UTF-8 then unescapes the value, using custom entities. /// /// This is normally the value you are interested in. Escape sequences such as `>` are /// replaced with their unescaped equivalents such as `>`. /// A fallback resolver for additional custom entities can be provided via /// `resolve_entity`. /// /// This will allocate if the value contains any escape sequences. /// /// See also [`unescape_value()`](Self::unescape_value) /// /// This method is available only if [`encoding`] feature is **not** enabled. /// /// [`encoding`]: ../../index.html#encoding #[cfg(any(doc, not(feature = "encoding")))] #[inline] pubfn unescape_value_with<'entity>(
&self,
resolve_entity: impl FnMut(&str) -> Option<&'entity str>,
) -> XmlResult<Cow<'a, str>> { self.decode_and_unescape_value_with(Decoder::utf8(), resolve_entity)
}
/// Decodes then unescapes the value. /// /// This will allocate if the value contains any escape sequences or in /// non-UTF-8 encoding. pubfn decode_and_unescape_value(&self, decoder: Decoder) -> XmlResult<Cow<'a, str>> { self.decode_and_unescape_value_with(decoder, resolve_predefined_entity)
}
/// Decodes then unescapes the value with custom entities. /// /// This will allocate if the value contains any escape sequences or in /// non-UTF-8 encoding. pubfn decode_and_unescape_value_with<'entity>(
&self,
decoder: Decoder,
resolve_entity: impl FnMut(&str) -> Option<&'entity str>,
) -> XmlResult<Cow<'a, str>> { let decoded = decoder.decode_cow(&self.value)?;
match unescape_with(&decoded, resolve_entity)? { // Because result is borrowed, no replacements was done and we can use original string
Cow::Borrowed(_) => Ok(decoded),
Cow::Owned(s) => Ok(s.into()),
}
}
/// If attribute value [represents] valid boolean values, returns `Some`, otherwise returns `None`. /// /// The valid boolean representations are only `"true"`, `"false"`, `"1"`, and `"0"`. /// /// # Examples /// /// ``` /// # use pretty_assertions::assert_eq; /// use quick_xml::events::attributes::Attribute; /// /// let attr = Attribute::from(("attr", "false")); /// assert_eq!(attr.as_bool(), Some(false)); /// /// let attr = Attribute::from(("attr", "0")); /// assert_eq!(attr.as_bool(), Some(false)); /// /// let attr = Attribute::from(("attr", "true")); /// assert_eq!(attr.as_bool(), Some(true)); /// /// let attr = Attribute::from(("attr", "1")); /// assert_eq!(attr.as_bool(), Some(true)); /// /// let attr = Attribute::from(("attr", "bot bool")); /// assert_eq!(attr.as_bool(), None); /// ``` /// /// [represents]: https://www.w3.org/TR/xmlschema11-2/#boolean #[inline] pubfn as_bool(&self) -> Option<bool> { matchself.value.as_ref() {
b"1" | b"true" => Some(true),
b"0" | b"false" => Some(false),
_ => None,
}
}
}
/// Iterator over XML attributes. /// /// Yields `Result<Attribute>`. An `Err` will be yielded if an attribute is malformed or duplicated. /// The duplicate check can be turned off by calling [`with_checks(false)`]. /// /// [`with_checks(false)`]: Self::with_checks #[derive(Clone)] pubstruct Attributes<'a> { /// Slice of `BytesStart` corresponding to attributes
bytes: &'a [u8], /// Iterator state, independent from the actual source of bytes
state: IterState,
}
impl<'a> Attributes<'a> { /// Internal constructor, used by `BytesStart`. Supplies data in reader's encoding #[inline] pub(crate) constfn wrap(buf: &'a [u8], pos: usize, html: bool) -> Self { Self {
bytes: buf,
state: IterState::new(pos, html),
}
}
/// Creates a new attribute iterator from a buffer. pubconstfn new(buf: &'a str, pos: usize) -> Self { Self::wrap(buf.as_bytes(), pos, false)
}
/// Creates a new attribute iterator from a buffer, allowing HTML attribute syntax. pubconstfn html(buf: &'a str, pos: usize) -> Self { Self::wrap(buf.as_bytes(), pos, true)
}
/// Changes whether attributes should be checked for uniqueness. /// /// The XML specification requires attribute keys in the same element to be unique. This check /// can be disabled to improve performance slightly. /// /// (`true` by default) pubfn with_checks(&mutself, val: bool) -> &>mut Attributes<'a> { self.state.check_duplicates = val; self
}
/// Checks if the current tag has a [`xsi:nil`] attribute. This method ignores any errors in /// attributes. /// /// # Examples /// /// ``` /// # use pretty_assertions::assert_eq; /// use quick_xml::events::Event; /// use quick_xml::name::QName; /// use quick_xml::reader::NsReader; /// /// let mut reader = NsReader::from_str(" /// <root xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'> /// <true xsi:nil='true'/> /// <false xsi:nil='false'/> /// <none/> /// <non-xsi xsi:nil='true' xmlns:xsi='namespace'/> /// <unbound-nil nil='true' xmlns='http://www.w3.org/2001/XMLSchema-instance'/> /// <another-xmlns f:nil='true' xmlns:f='http://www.w3.org/2001/XMLSchema-instance'/> /// </root> /// "); /// reader.config_mut().trim_text(true); /// /// macro_rules! check { /// ($reader:expr, $name:literal, $value:literal) => { /// let event = match $reader.read_event().unwrap() { /// Event::Empty(e) => e, /// e => panic!("Unexpected event {:?}", e), /// }; /// assert_eq!( /// (event.name(), event.attributes().has_nil(&$reader)), /// (QName($name.as_bytes()), $value), /// ); /// }; /// } /// /// let root = match reader.read_event().unwrap() { /// Event::Start(e) => e, /// e => panic!("Unexpected event {:?}", e), /// }; /// assert_eq!(root.attributes().has_nil(&reader), false); /// /// // definitely true /// check!(reader, "true", true); /// // definitely false /// check!(reader, "false", false); /// // absence of the attribute means that attribute is not set /// check!(reader, "none", false); /// // attribute not bound to the correct namespace /// check!(reader, "non-xsi", false); /// // attributes without prefix not bound to any namespace /// check!(reader, "unbound-nil", false); /// // prefix can be any while it is bound to the correct namespace /// check!(reader, "another-xmlns", true); /// ``` /// /// [`xsi:nil`]: https://www.w3.org/TR/xmlschema-1/#xsi_nil pubfn has_nil<R>(&mutself, reader: &NsReader<R>) -> bool { usecrate::name::ResolveResult::*;
/// Errors that can be raised during parsing attributes. /// /// Recovery position in examples shows the position from which parsing of the /// next attribute will be attempted. #[derive(Clone, Debug, PartialEq, Eq)] pubenum AttrError { /// Attribute key was not followed by `=`, position relative to the start of /// the owning tag is provided. /// /// Example of input that raises this error: /// /// ```xml /// <tag key another="attribute"/> /// <!-- ^~~ error position, recovery position (8) --> /// ``` /// /// This error can be raised only when the iterator is in XML mode.
ExpectedEq(usize), /// Attribute value was not found after `=`, position relative to the start /// of the owning tag is provided. /// /// Example of input that raises this error: /// /// ```xml /// <tag key = /> /// <!-- ^~~ error position, recovery position (10) --> /// ``` /// /// This error can be returned only for the last attribute in the list, /// because otherwise any content after `=` will be threated as a value. /// The XML /// /// ```xml /// <tag key = another-key = "value"/> /// <!-- ^ ^- recovery position (24) --> /// <!-- '~~ error position (22) --> /// ``` /// /// will be treated as `Attribute { key = b"key", value = b"another-key" }` /// and or [`Attribute`] is returned, or [`AttrError::UnquotedValue`] is raised, /// depending on the parsing mode.
ExpectedValue(usize), /// Attribute value is not quoted, position relative to the start of the /// owning tag is provided. /// /// Example of input that raises this error: /// /// ```xml /// <tag key = value /> /// <!-- ^ ^~~ recovery position (15) --> /// <!-- '~~ error position (10) --> /// ``` /// /// This error can be raised only when the iterator is in XML mode.
UnquotedValue(usize), /// Attribute value was not finished with a matching quote, position relative /// to the start of owning tag and a quote is provided. That position is always /// a last character in the tag content. /// /// Example of input that raises this error: /// /// ```xml /// <tag key = "value /> /// <tag key = 'value /> /// <!-- ^~~ error position, recovery position (18) --> /// ``` /// /// This error can be returned only for the last attribute in the list, /// because all input was consumed during scanning for a quote.
ExpectedQuote(usize, u8), /// An attribute with the same name was already encountered. Two parameters /// define (1) the error position relative to the start of the owning tag /// for a new attribute and (2) the start position of a previously encountered /// attribute with the same name. /// /// Example of input that raises this error: /// /// ```xml /// <tag key = 'value' key="value2" attr3='value3' /> /// <!-- ^ ^ ^~~ recovery position (32) --> /// <!-- | '~~ error position (19) --> /// <!-- '~~ previous position (4) --> /// ``` /// /// This error is returned only when [`Attributes::with_checks()`] is set /// to `true` (that is default behavior).
Duplicated(usize, usize),
}
impl Display for AttrError { fn fmt(&self, f: &mut Formatter) -> fmt::Result { matchself { 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
),
}
}
}
/// A struct representing a key/value XML or HTML [attribute]. /// /// [attribute]: https://www.w3.org/TR/xml11/#NT-Attribute #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pubenum Attr<T> { /// Attribute with value enclosed in double quotes (`"`). Attribute key and /// value provided. This is a canonical XML-style attribute.
DoubleQ(T, T), /// Attribute with value enclosed in single quotes (`'`). Attribute key and /// value provided. This is an XML-style attribute.
SingleQ(T, T), /// Attribute with value not enclosed in quotes. Attribute key and value /// provided. This is HTML-style attribute, it can be returned in HTML-mode /// parsing only. In an XML mode [`AttrError::UnquotedValue`] will be raised /// instead. /// /// Attribute value can be invalid according to the [HTML specification], /// in particular, it can contain `"`, `'`, `=`, `<`, and <code>`</code> /// characters. The absence of the `>` character is nevertheless guaranteed, /// since the parser extracts [events] based on them even before the start /// of parsing attributes. /// /// [HTML specification]: https://html.spec.whatwg.org/#unquoted /// [events]: crate::events::Event::Start
Unquoted(T, T), /// Attribute without value. Attribute key provided. This is HTML-style attribute, /// it can be returned in HTML-mode parsing only. In XML mode /// [`AttrError::ExpectedEq`] will be raised instead.
Empty(T),
}
impl<T> Attr<T> { /// Maps an `Attr<T>` to `Attr<U>` by applying a function to a contained key and value. #[inline] pubfn map<U, F>(self, mut f: F) -> Attr<U> where
F: FnMut(T) -> U,
{ matchself {
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]> { /// Returns the key value #[inline] pubconstfn key(&self) -> QName<'a> {
QName(matchself {
Attr::DoubleQ(key, _) => key,
Attr::SingleQ(key, _) => key,
Attr::Empty(key) => key,
Attr::Unquoted(key, _) => key,
})
} /// Returns the attribute value. For [`Self::Empty`] variant an empty slice /// is returned according to the [HTML specification]. /// /// [HTML specification]: https://www.w3.org/TR/2012/WD-html-markup-20120329/syntax.html#syntax-attr-empty #[inline] pubconstfn value(&self) -> &'a [u8] { matchself {
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 { matchself {
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") // Comment to prevent formatting and keep style consistent
.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(),
}
}
}
/// Unpacks attribute key and value into tuple of this two elements. /// `None` value element is returned only for [`Attr::Empty`] variant. 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 { /// Iteration finished, iterator will return `None` to all [`IterState::next`] /// requests.
Done, /// The last attribute returned was deserialized successfully. Contains an /// offset from which next attribute should be searched.
Next(usize), /// The last attribute returns [`AttrError::UnquotedValue`], offset pointed /// to the beginning of the value. Recover should skip a value
SkipValue(usize), /// The last attribute returns [`AttrError::Duplicated`], offset pointed to /// the equal (`=`) sign. Recover should skip it and a value
SkipEqValue(usize),
}
/// External iterator over spans of attribute key and value #[derive(Clone, Debug)] pub(crate) struct IterState { /// Iteration state that determines what actions should be done before the /// actual parsing of the next attribute
state: State, /// If `true`, enables ability to parse unquoted values and key-only (empty) /// attributes
html: bool, /// If `true`, checks for duplicate names
check_duplicates: bool, /// If `check_duplicates` is set, contains the ranges of already parsed attribute /// names. We store a ranges instead of slices to able to report a previous /// attribute position
keys: Vec<Range<usize>>,
}
/// Recover from an error that could have been made on a previous step. /// Returns an offset from which parsing should continue. /// If there no input left, returns `None`. fn recover(&self, slice: &[u8]) -> Option<usize> { matchself.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),
}
}
/// Skip all characters up to first space symbol or end-of-input #[inline] #[allow(clippy::manual_map)] fn skip_value(&self, slice: &[u8], offset: usize) -> Option<usize> { letmut iter = (offset..).zip(slice[offset..].iter());
/// Skip all characters up to first space symbol or end-of-input #[inline] fn skip_eq_value(&self, slice: &[u8], offset: usize) -> Option<usize> { letmut iter = (offset..).zip(slice[offset..].iter());
// Skip all up to the quote and get the quote type let quote = match iter.find(|(_, &b)| !is_whitespace(b)) { // Input: ` key = "` // | ^ // offset
Some((_, b'"')) => b'"', // Input: ` key = '` // | ^ // offset
Some((_, b'\'')) => b'\'',
/// # Parameters /// /// - `slice`: content of the tag, used for checking for duplicates /// - `key`: Range of key in slice, if iterator in HTML mode /// - `offset`: Position of error if iterator in XML mode #[inline] fn key_only(&mutself, slice: &[u8], key: Range<usize>, offset: usize) -> Option<AttrResult> {
Some(ifself.html { self.check_for_duplicates(slice, key).map(Attr::Empty)
} else {
Err(AttrError::ExpectedEq(offset))
})
}
// Index where next key started let start_key = match iter.find(|(_, &b)| !is_whitespace(b)) { // Input: ` key` // ^
Some((s, _)) => s, // Input: ` ` // ^
None => { // Because we reach end-of-input, stop iteration on next call self.state = State::Done; return None;
}
}; // Span of a key let (key, offset) = match iter.find(|(_, &b)| b == b'=' || is_whitespace(b)) { // Input: ` key=` // | ^ // s e
Some((e, b'=')) => (start_key..e, e),
// Input: ` key ` // ^
Some((e, _)) => match iter.find(|(_, &b)| !is_whitespace(b)) { // Input: ` key =` // | | ^ // start_key e
Some((offset, b'=')) => (start_key..e, offset), // Input: ` key x` // | | ^ // start_key e // If HTML-like attributes is allowed, this is the result, otherwise error
Some((offset, _)) => { // In any case, recovering is not required self.state = State::Next(offset); returnself.key_only(slice, start_key..e, offset);
} // Input: ` key ` // | | ^ // start_key e // If HTML-like attributes is allowed, this is the result, otherwise error
None => { // Because we reach end-of-input, stop iteration on next call self.state = State::Done; returnself.key_only(slice, start_key..e, slice.len());
}
},
// Input: ` key` // | ^ // s e = len() // If HTML-like attributes is allowed, this is the result, otherwise error
None => { // Because we reach end-of-input, stop iteration on next call self.state = State::Done; let e = slice.len(); returnself.key_only(slice, start_key..e, e);
}
};
// Gets the position of quote and quote type let (start_value, quote) = match iter.find(|(_, &b)| !is_whitespace(b)) { // Input: ` key = "` // ^
Some((s, b'"')) => (s + 1, b'"'), // Input: ` key = '` // ^
Some((s, b'\'')) => (s + 1, b'\''),
// Input: ` key = x` // ^ // If HTML-like attributes is allowed, this is the start of the value
Some((s, _)) ifself.html => { // We do not check validity of attribute value characters as required // according to https://html.spec.whatwg.org/#unquoted. It can be done // during validation phase let end = match iter.find(|(_, &b)| is_whitespace(b)) { // Input: ` key = value ` // | ^ // s e
Some((e, _)) => e, // Input: ` key = value` // | ^ // s e = len()
None => slice.len(),
}; self.state = State::Next(end); return Some(Ok(Attr::Unquoted(key, s..end)));
} // Input: ` key = x` // ^
Some((s, _)) => { self.state = State::SkipValue(s); return Some(Err(AttrError::UnquotedValue(s)));
}
// Input: ` key = ` // ^
None => { // Because we reach end-of-input, stop iteration on next call self.state = State::Done; return Some(Err(AttrError::ExpectedValue(slice.len())));
}
};
/// Checks, how parsing of XML-style attributes works. Each attribute should /// have a value, enclosed in single or double quotes. #[cfg(test)] mod xml { usesuper::*; use pretty_assertions::assert_eq;
/// Checked attribute is the single attribute mod single { usesuper::*; use pretty_assertions::assert_eq;
/// Attribute have a value enclosed in single quotes #[test] fn single_quoted() { letmut iter = Attributes::new(r#"tag key='value'"#, 3);
/// Key is started with an invalid symbol (a single quote in this test). /// Because we do not check validity of keys and values during parsing, /// that invalid attribute will be returned #[test] fn key_start_invalid() { letmut iter = Attributes::new(r#"tag 'key'='value'"#, 3);
/// Key contains an invalid symbol (an ampersand in this test). /// Because we do not check validity of keys and values during parsing, /// that invalid attribute will be returned #[test] fn key_contains_invalid() { letmut iter = Attributes::new(r#"tag key&jey='value'"#, 3);
/// Checked attribute is the first attribute in the list of many attributes mod first { usesuper::*; use pretty_assertions::assert_eq;
/// Attribute have a value enclosed in single quotes #[test] fn single_quoted() { letmut iter = Attributes::new(r#"tag key='value' regular='attribute'"#, 3);
/// Attribute have a value enclosed in double quotes #[test] fn double_quoted() { letmut iter = Attributes::new(r#"tag key="value" regular='attribute'"#, 3);
/// Attribute have a value, not enclosed in quotes #[test] fn unquoted() { letmut iter = Attributes::new(r#"tag key=value regular='attribute'"#, 3); // 0 ^ = 8
/// Key is started with an invalid symbol (a single quote in this test). /// Because we do not check validity of keys and values during parsing, /// that invalid attribute will be returned #[test] fn key_start_invalid() { letmut iter = Attributes::new(r#"tag 'key'='value' regular='attribute'"#, 3);
/// Key contains an invalid symbol (an ampersand in this test). /// Because we do not check validity of keys and values during parsing, /// that invalid attribute will be returned #[test] fn key_contains_invalid() { letmut iter = Attributes::new(r#"tag key&jey='value' regular='attribute'"#, 3);
/// Attribute value is missing after `=`. #[test] fn missed_value() { letmut iter = Attributes::new(r#"tag key= regular='attribute'"#, 3); // 0 ^ = 9
assert_eq!(iter.next(), Some(Err(AttrError::UnquotedValue(9)))); // Because we do not check validity of keys and values during parsing, // "error='recovery'" is considered, as unquoted attribute value and // skipped during recovery and iteration finished
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
// In that case "regular=" considered as unquoted value
assert_eq!(iter.next(), Some(Err(AttrError::UnquotedValue(9)))); // In that case "'attribute'" considered as a key, because we do not check // validity of key names
assert_eq!(iter.next(), Some(Err(AttrError::ExpectedEq(29))));
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
// In that case "regular" considered as unquoted value
assert_eq!(iter.next(), Some(Err(AttrError::UnquotedValue(9)))); // In that case "='attribute'" considered as a key, because we do not check // validity of key names
assert_eq!(iter.next(), Some(Err(AttrError::ExpectedEq(29))));
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), Some(Err(AttrError::UnquotedValue(9)))); // In that case second "=" considered as a key, because we do not check // validity of key names
assert_eq!(iter.next(), Some(Err(AttrError::ExpectedEq(19)))); // In that case "'attribute'" considered as a key, because we do not check // validity of key names
assert_eq!(iter.next(), Some(Err(AttrError::ExpectedEq(30))));
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
}
}
/// Copy of single, but with additional spaces in markup mod sparsed { usesuper::*; use pretty_assertions::assert_eq;
/// Attribute have a value enclosed in single quotes #[test] fn single_quoted() { letmut iter = Attributes::new(r#"tag key = 'value'"#, 3);
/// Key is started with an invalid symbol (a single quote in this test). /// Because we do not check validity of keys and values during parsing, /// that invalid attribute will be returned #[test] fn key_start_invalid() { letmut iter = Attributes::new(r#"tag 'key' = 'value'"#, 3);
/// Key contains an invalid symbol (an ampersand in this test). /// Because we do not check validity of keys and values during parsing, /// that invalid attribute will be returned #[test] fn key_contains_invalid() { letmut iter = Attributes::new(r#"tag key&jey = 'value'"#, 3);
/// Checks that duplicated attributes correctly reported and recovering is /// possible after that mod duplicated { usesuper::*;
mod with_check { usesuper::*; use pretty_assertions::assert_eq;
/// Attribute have a value enclosed in single quotes #[test] fn single_quoted() { letmut iter = Attributes::new(r#"tag key='value' key='dup' another=''"#, 3); // 0 ^ = 4 ^ = 16
/// Check for duplicated names is disabled mod without_check { usesuper::*; use pretty_assertions::assert_eq;
/// Attribute have a value enclosed in single quotes #[test] fn single_quoted() { letmut iter = Attributes::new(r#"tag key='value' key='dup' another=''"#, 3);
iter.with_checks(false);
/// Attribute have a value enclosed in double quotes #[test] fn double_quoted() { letmut iter = Attributes::new(r#"tag key='value' key="dup" another=''"#, 3);
iter.with_checks(false);
/// Checks, how parsing of HTML-style attributes works. Each attribute can be /// in three forms: /// - XML-like: have a value, enclosed in single or double quotes /// - have a value, do not enclosed in quotes /// - without value, key only #[cfg(test)] mod html { usesuper::*; use pretty_assertions::assert_eq;
/// Checked attribute is the single attribute mod single { usesuper::*; use pretty_assertions::assert_eq;
/// Attribute have a value enclosed in single quotes #[test] fn single_quoted() { letmut iter = Attributes::html(r#"tag key='value'"#, 3);
/// Key is started with an invalid symbol (a single quote in this test). /// Because we do not check validity of keys and values during parsing, /// that invalid attribute will be returned #[test] fn key_start_invalid() { letmut iter = Attributes::html(r#"tag 'key'='value'"#, 3);
/// Key contains an invalid symbol (an ampersand in this test). /// Because we do not check validity of keys and values during parsing, /// that invalid attribute will be returned #[test] fn key_contains_invalid() { letmut iter = Attributes::html(r#"tag key&jey='value'"#, 3);
/// Checked attribute is the first attribute in the list of many attributes mod first { usesuper::*; use pretty_assertions::assert_eq;
/// Attribute have a value enclosed in single quotes #[test] fn single_quoted() { letmut iter = Attributes::html(r#"tag key='value' regular='attribute'"#, 3);
/// Attribute have a value enclosed in double quotes #[test] fn double_quoted() { letmut iter = Attributes::html(r#"tag key="value" regular='attribute'"#, 3);
/// Key is started with an invalid symbol (a single quote in this test). /// Because we do not check validity of keys and values during parsing, /// that invalid attribute will be returned #[test] fn key_start_invalid() { letmut iter = Attributes::html(r#"tag 'key'='value' regular='attribute'"#, 3);
/// Key contains an invalid symbol (an ampersand in this test). /// Because we do not check validity of keys and values during parsing, /// that invalid attribute will be returned #[test] fn key_contains_invalid() { letmut iter = Attributes::html(r#"tag key&jey='value' regular='attribute'"#, 3);
/// Attribute value is missing after `=` #[test] fn missed_value() { letmut iter = Attributes::html(r#"tag key= regular='attribute'"#, 3);
// Because we do not check validity of keys and values during parsing, // "regular='attribute'" is considered as unquoted attribute value
assert_eq!(
iter.next(),
Some(Ok(Attribute {
key: QName(b"key"),
value: Cow::Borrowed(b"regular='attribute'"),
}))
);
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
letmut iter = Attributes::html(r#"tag key= regular= 'attribute'"#, 3);
// Because we do not check validity of keys and values during parsing, // "regular=" is considered as unquoted attribute value
assert_eq!(
iter.next(),
Some(Ok(Attribute {
key: QName(b"key"),
value: Cow::Borrowed(b"regular="),
}))
); // Because we do not check validity of keys and values during parsing, // "'attribute'" is considered as key-only attribute
assert_eq!(
iter.next(),
Some(Ok(Attribute {
key: QName(b"'attribute'"),
value: Cow::Borrowed(&[]),
}))
);
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
letmut iter = Attributes::html(r#"tag key= regular ='attribute'"#, 3);
// Because we do not check validity of keys and values during parsing, // "regular" is considered as unquoted attribute value
assert_eq!(
iter.next(),
Some(Ok(Attribute {
key: QName(b"key"),
value: Cow::Borrowed(b"regular"),
}))
); // Because we do not check validity of keys and values during parsing, // "='attribute'" is considered as key-only attribute
assert_eq!(
iter.next(),
Some(Ok(Attribute {
key: QName(b"='attribute'"),
value: Cow::Borrowed(&[]),
}))
);
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
// Because we do not check validity of keys and values during parsing, // "regular" is considered as unquoted attribute value
assert_eq!(
iter.next(),
Some(Ok(Attribute {
key: QName(b"key"),
value: Cow::Borrowed(b"regular"),
}))
); // Because we do not check validity of keys and values during parsing, // "=" is considered as key-only attribute
assert_eq!(
iter.next(),
Some(Ok(Attribute {
key: QName(b"="),
value: Cow::Borrowed(&[]),
}))
); // Because we do not check validity of keys and values during parsing, // "'attribute'" is considered as key-only attribute
assert_eq!(
iter.next(),
Some(Ok(Attribute {
key: QName(b"'attribute'"),
value: Cow::Borrowed(&[]),
}))
);
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
}
}
/// Copy of single, but with additional spaces in markup mod sparsed { usesuper::*; use pretty_assertions::assert_eq;
/// Attribute have a value enclosed in single quotes #[test] fn single_quoted() { letmut iter = Attributes::html(r#"tag key = 'value'"#, 3);
/// Key is started with an invalid symbol (a single quote in this test). /// Because we do not check validity of keys and values during parsing, /// that invalid attribute will be returned #[test] fn key_start_invalid() { letmut iter = Attributes::html(r#"tag 'key' = 'value'"#, 3);
/// Key contains an invalid symbol (an ampersand in this test). /// Because we do not check validity of keys and values during parsing, /// that invalid attribute will be returned #[test] fn key_contains_invalid() { letmut iter = Attributes::html(r#"tag key&jey = 'value'"#, 3);
/// Checks that duplicated attributes correctly reported and recovering is /// possible after that mod duplicated { usesuper::*;
mod with_check { usesuper::*; use pretty_assertions::assert_eq;
/// Attribute have a value enclosed in single quotes #[test] fn single_quoted() { letmut iter = Attributes::html(r#"tag key='value' key='dup' another=''"#, 3); // 0 ^ = 4 ^ = 16
/// Check for duplicated names is disabled mod without_check { usesuper::*; use pretty_assertions::assert_eq;
/// Attribute have a value enclosed in single quotes #[test] fn single_quoted() { letmut iter = Attributes::html(r#"tag key='value' key='dup' another=''"#, 3);
iter.with_checks(false);
/// Attribute have a value enclosed in double quotes #[test] fn double_quoted() { letmut iter = Attributes::html(r#"tag key='value' key="dup" another=''"#, 3);
iter.with_checks(false);
/// Attribute have a value, not enclosed in quotes #[test] fn unquoted() { letmut iter = Attributes::html(r#"tag key='value' key=dup another=''"#, 3);
iter.with_checks(false);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.