| 1 | //! Xml Attributes module
|
| 2 | //!
|
| 3 | //! Provides an iterator over attributes key/value pairs
|
| 4 |
|
| 5 | use crate::errors::Result as XmlResult;
|
| 6 | use crate::escape::{escape, unescape_with};
|
| 7 | use crate::name::QName;
|
| 8 | use crate::reader::{is_whitespace, Reader};
|
| 9 | use crate::utils::{write_byte_string, write_cow_string, Bytes};
|
| 10 | use std::fmt::{self, Debug, Display, Formatter};
|
| 11 | use std::iter::FusedIterator;
|
| 12 | use std::{borrow::Cow, ops::Range};
|
| 13 |
|
| 14 | /// A struct representing a key/value XML attribute.
|
| 15 | ///
|
| 16 | /// Field `value` stores raw bytes, possibly containing escape-sequences. Most users will likely
|
| 17 | /// want to access the value using one of the [`unescape_value`] and [`decode_and_unescape_value`]
|
| 18 | /// functions.
|
| 19 | ///
|
| 20 | /// [`unescape_value`]: Self::unescape_value
|
| 21 | /// [`decode_and_unescape_value`]: Self::decode_and_unescape_value
|
| 22 | #[derive (Clone, Eq, PartialEq)]
|
| 23 | pub struct Attribute<'a> {
|
| 24 | /// The key to uniquely define the attribute.
|
| 25 | ///
|
| 26 | /// If [`Attributes::with_checks`] is turned off, the key might not be unique.
|
| 27 | pub key: QName<'a>,
|
| 28 | /// The raw value of the attribute.
|
| 29 | pub value: Cow<'a, [u8]>,
|
| 30 | }
|
| 31 |
|
| 32 | impl<'a> Attribute<'a> {
|
| 33 | /// Decodes using UTF-8 then unescapes the value.
|
| 34 | ///
|
| 35 | /// This is normally the value you are interested in. Escape sequences such as `>` are
|
| 36 | /// replaced with their unescaped equivalents such as `>`.
|
| 37 | ///
|
| 38 | /// This will allocate if the value contains any escape sequences.
|
| 39 | ///
|
| 40 | /// See also [`unescape_value_with()`](Self::unescape_value_with)
|
| 41 | ///
|
| 42 | /// This method is available only if `encoding` feature is **not** enabled.
|
| 43 | #[cfg (any(doc, not(feature = "encoding" )))]
|
| 44 | pub fn unescape_value(&self) -> XmlResult<Cow<'a, str>> {
|
| 45 | self.unescape_value_with(|_| None)
|
| 46 | }
|
| 47 |
|
| 48 | /// Decodes using UTF-8 then unescapes the value, using custom entities.
|
| 49 | ///
|
| 50 | /// This is normally the value you are interested in. Escape sequences such as `>` are
|
| 51 | /// replaced with their unescaped equivalents such as `>`.
|
| 52 | /// A fallback resolver for additional custom entities can be provided via
|
| 53 | /// `resolve_entity`.
|
| 54 | ///
|
| 55 | /// This will allocate if the value contains any escape sequences.
|
| 56 | ///
|
| 57 | /// See also [`unescape_value()`](Self::unescape_value)
|
| 58 | ///
|
| 59 | /// This method is available only if `encoding` feature is **not** enabled.
|
| 60 | #[cfg (any(doc, not(feature = "encoding" )))]
|
| 61 | pub fn unescape_value_with<'entity>(
|
| 62 | &self,
|
| 63 | resolve_entity: impl FnMut(&str) -> Option<&'entity str>,
|
| 64 | ) -> XmlResult<Cow<'a, str>> {
|
| 65 | // from_utf8 should never fail because content is always UTF-8 encoded
|
| 66 | let decoded = match &self.value {
|
| 67 | Cow::Borrowed(bytes) => Cow::Borrowed(std::str::from_utf8(bytes)?),
|
| 68 | // Convert to owned, because otherwise Cow will be bound with wrong lifetime
|
| 69 | Cow::Owned(bytes) => Cow::Owned(std::str::from_utf8(bytes)?.to_string()),
|
| 70 | };
|
| 71 |
|
| 72 | match unescape_with(&decoded, resolve_entity)? {
|
| 73 | // Because result is borrowed, no replacements was done and we can use original string
|
| 74 | Cow::Borrowed(_) => Ok(decoded),
|
| 75 | Cow::Owned(s) => Ok(s.into()),
|
| 76 | }
|
| 77 | }
|
| 78 |
|
| 79 | /// Decodes then unescapes the value.
|
| 80 | ///
|
| 81 | /// This will allocate if the value contains any escape sequences or in
|
| 82 | /// non-UTF-8 encoding.
|
| 83 | pub fn decode_and_unescape_value<B>(&self, reader: &Reader<B>) -> XmlResult<Cow<'a, str>> {
|
| 84 | self.decode_and_unescape_value_with(reader, |_| None)
|
| 85 | }
|
| 86 |
|
| 87 | /// Decodes then unescapes the value with custom entities.
|
| 88 | ///
|
| 89 | /// This will allocate if the value contains any escape sequences or in
|
| 90 | /// non-UTF-8 encoding.
|
| 91 | pub fn decode_and_unescape_value_with<'entity, B>(
|
| 92 | &self,
|
| 93 | reader: &Reader<B>,
|
| 94 | resolve_entity: impl FnMut(&str) -> Option<&'entity str>,
|
| 95 | ) -> XmlResult<Cow<'a, str>> {
|
| 96 | let decoded = match &self.value {
|
| 97 | Cow::Borrowed(bytes) => reader.decoder().decode(bytes)?,
|
| 98 | // Convert to owned, because otherwise Cow will be bound with wrong lifetime
|
| 99 | Cow::Owned(bytes) => reader.decoder().decode(bytes)?.into_owned().into(),
|
| 100 | };
|
| 101 |
|
| 102 | match unescape_with(&decoded, resolve_entity)? {
|
| 103 | // Because result is borrowed, no replacements was done and we can use original string
|
| 104 | Cow::Borrowed(_) => Ok(decoded),
|
| 105 | Cow::Owned(s) => Ok(s.into()),
|
| 106 | }
|
| 107 | }
|
| 108 | }
|
| 109 |
|
| 110 | impl<'a> Debug for Attribute<'a> {
|
| 111 | fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
| 112 | write!(f, "Attribute {{ key: " )?;
|
| 113 | write_byte_string(f, self.key.as_ref())?;
|
| 114 | write!(f, ", value: " )?;
|
| 115 | write_cow_string(f, &self.value)?;
|
| 116 | write!(f, " }}" )
|
| 117 | }
|
| 118 | }
|
| 119 |
|
| 120 | impl<'a> From<(&'a [u8], &'a [u8])> for Attribute<'a> {
|
| 121 | /// Creates new attribute from raw bytes.
|
| 122 | /// Does not apply any transformation to both key and value.
|
| 123 | ///
|
| 124 | /// # Examples
|
| 125 | ///
|
| 126 | /// ```
|
| 127 | /// # use pretty_assertions::assert_eq;
|
| 128 | /// use quick_xml::events::attributes::Attribute;
|
| 129 | ///
|
| 130 | /// let features = Attribute::from(("features" .as_bytes(), "Bells & whistles" .as_bytes()));
|
| 131 | /// assert_eq!(features.value, "Bells & whistles" .as_bytes());
|
| 132 | /// ```
|
| 133 | fn from(val: (&'a [u8], &'a [u8])) -> Attribute<'a> {
|
| 134 | Attribute {
|
| 135 | key: QName(val.0),
|
| 136 | value: Cow::from(val.1),
|
| 137 | }
|
| 138 | }
|
| 139 | }
|
| 140 |
|
| 141 | impl<'a> From<(&'a str, &'a str)> for Attribute<'a> {
|
| 142 | /// Creates new attribute from text representation.
|
| 143 | /// Key is stored as-is, but the value will be escaped.
|
| 144 | ///
|
| 145 | /// # Examples
|
| 146 | ///
|
| 147 | /// ```
|
| 148 | /// # use pretty_assertions::assert_eq;
|
| 149 | /// use quick_xml::events::attributes::Attribute;
|
| 150 | ///
|
| 151 | /// let features = Attribute::from(("features" , "Bells & whistles" ));
|
| 152 | /// assert_eq!(features.value, "Bells & whistles" .as_bytes());
|
| 153 | /// ```
|
| 154 | fn from(val: (&'a str, &'a str)) -> Attribute<'a> {
|
| 155 | Attribute {
|
| 156 | key: QName(val.0.as_bytes()),
|
| 157 | value: match escape(raw:val.1) {
|
| 158 | Cow::Borrowed(s: &str) => Cow::Borrowed(s.as_bytes()),
|
| 159 | Cow::Owned(s: String) => Cow::Owned(s.into_bytes()),
|
| 160 | },
|
| 161 | }
|
| 162 | }
|
| 163 | }
|
| 164 |
|
| 165 | impl<'a> From<Attr<&'a [u8]>> for Attribute<'a> {
|
| 166 | #[inline ]
|
| 167 | fn from(attr: Attr<&'a [u8]>) -> Self {
|
| 168 | Self {
|
| 169 | key: attr.key(),
|
| 170 | value: Cow::Borrowed(attr.value()),
|
| 171 | }
|
| 172 | }
|
| 173 | }
|
| 174 |
|
| 175 | ////////////////////////////////////////////////////////////////////////////////////////////////////
|
| 176 |
|
| 177 | /// Iterator over XML attributes.
|
| 178 | ///
|
| 179 | /// Yields `Result<Attribute>`. An `Err` will be yielded if an attribute is malformed or duplicated.
|
| 180 | /// The duplicate check can be turned off by calling [`with_checks(false)`].
|
| 181 | ///
|
| 182 | /// [`with_checks(false)`]: Self::with_checks
|
| 183 | #[derive (Clone, Debug)]
|
| 184 | pub struct Attributes<'a> {
|
| 185 | /// Slice of `BytesStart` corresponding to attributes
|
| 186 | bytes: &'a [u8],
|
| 187 | /// Iterator state, independent from the actual source of bytes
|
| 188 | state: IterState,
|
| 189 | }
|
| 190 |
|
| 191 | impl<'a> Attributes<'a> {
|
| 192 | /// Internal constructor, used by `BytesStart`. Supplies data in reader's encoding
|
| 193 | #[inline ]
|
| 194 | pub(crate) fn wrap(buf: &'a [u8], pos: usize, html: bool) -> Self {
|
| 195 | Self {
|
| 196 | bytes: buf,
|
| 197 | state: IterState::new(pos, html),
|
| 198 | }
|
| 199 | }
|
| 200 |
|
| 201 | /// Creates a new attribute iterator from a buffer.
|
| 202 | pub fn new(buf: &'a str, pos: usize) -> Self {
|
| 203 | Self::wrap(buf.as_bytes(), pos, false)
|
| 204 | }
|
| 205 |
|
| 206 | /// Creates a new attribute iterator from a buffer, allowing HTML attribute syntax.
|
| 207 | pub fn html(buf: &'a str, pos: usize) -> Self {
|
| 208 | Self::wrap(buf.as_bytes(), pos, true)
|
| 209 | }
|
| 210 |
|
| 211 | /// Changes whether attributes should be checked for uniqueness.
|
| 212 | ///
|
| 213 | /// The XML specification requires attribute keys in the same element to be unique. This check
|
| 214 | /// can be disabled to improve performance slightly.
|
| 215 | ///
|
| 216 | /// (`true` by default)
|
| 217 | pub fn with_checks(&mut self, val: bool) -> &mut Attributes<'a> {
|
| 218 | self.state.check_duplicates = val;
|
| 219 | self
|
| 220 | }
|
| 221 | }
|
| 222 |
|
| 223 | impl<'a> Iterator for Attributes<'a> {
|
| 224 | type Item = Result<Attribute<'a>, AttrError>;
|
| 225 |
|
| 226 | #[inline ]
|
| 227 | fn next(&mut self) -> Option<Self::Item> {
|
| 228 | match self.state.next(self.bytes) {
|
| 229 | None => None,
|
| 230 | Some(Ok(a: Attr>)) => Some(Ok(a.map(|range: Range| &self.bytes[range]).into())),
|
| 231 | Some(Err(e: AttrError)) => Some(Err(e)),
|
| 232 | }
|
| 233 | }
|
| 234 | }
|
| 235 |
|
| 236 | impl<'a> FusedIterator for Attributes<'a> {}
|
| 237 |
|
| 238 | ////////////////////////////////////////////////////////////////////////////////////////////////////
|
| 239 |
|
| 240 | /// Errors that can be raised during parsing attributes.
|
| 241 | ///
|
| 242 | /// Recovery position in examples shows the position from which parsing of the
|
| 243 | /// next attribute will be attempted.
|
| 244 | #[derive (Clone, Debug, PartialEq, Eq)]
|
| 245 | pub enum AttrError {
|
| 246 | /// Attribute key was not followed by `=`, position relative to the start of
|
| 247 | /// the owning tag is provided.
|
| 248 | ///
|
| 249 | /// Example of input that raises this error:
|
| 250 | ///
|
| 251 | /// ```xml
|
| 252 | /// <tag key another="attribute"/>
|
| 253 | /// <!-- ^~~ error position, recovery position (8) -->
|
| 254 | /// ```
|
| 255 | ///
|
| 256 | /// This error can be raised only when the iterator is in XML mode.
|
| 257 | ExpectedEq(usize),
|
| 258 | /// Attribute value was not found after `=`, position relative to the start
|
| 259 | /// of the owning tag is provided.
|
| 260 | ///
|
| 261 | /// Example of input that raises this error:
|
| 262 | ///
|
| 263 | /// ```xml
|
| 264 | /// <tag key = />
|
| 265 | /// <!-- ^~~ error position, recovery position (10) -->
|
| 266 | /// ```
|
| 267 | ///
|
| 268 | /// This error can be returned only for the last attribute in the list,
|
| 269 | /// because otherwise any content after `=` will be threated as a value.
|
| 270 | /// The XML
|
| 271 | ///
|
| 272 | /// ```xml
|
| 273 | /// <tag key = another-key = "value"/>
|
| 274 | /// <!-- ^ ^- recovery position (24) -->
|
| 275 | /// <!-- '~~ error position (22) -->
|
| 276 | /// ```
|
| 277 | ///
|
| 278 | /// will be treated as `Attribute { key = b"key", value = b"another-key" }`
|
| 279 | /// and or [`Attribute`] is returned, or [`AttrError::UnquotedValue`] is raised,
|
| 280 | /// depending on the parsing mode.
|
| 281 | ExpectedValue(usize),
|
| 282 | /// Attribute value is not quoted, position relative to the start of the
|
| 283 | /// owning tag is provided.
|
| 284 | ///
|
| 285 | /// Example of input that raises this error:
|
| 286 | ///
|
| 287 | /// ```xml
|
| 288 | /// <tag key = value />
|
| 289 | /// <!-- ^ ^~~ recovery position (15) -->
|
| 290 | /// <!-- '~~ error position (10) -->
|
| 291 | /// ```
|
| 292 | ///
|
| 293 | /// This error can be raised only when the iterator is in XML mode.
|
| 294 | UnquotedValue(usize),
|
| 295 | /// Attribute value was not finished with a matching quote, position relative
|
| 296 | /// to the start of owning tag and a quote is provided. That position is always
|
| 297 | /// a last character in the tag content.
|
| 298 | ///
|
| 299 | /// Example of input that raises this error:
|
| 300 | ///
|
| 301 | /// ```xml
|
| 302 | /// <tag key = "value />
|
| 303 | /// <tag key = 'value />
|
| 304 | /// <!-- ^~~ error position, recovery position (18) -->
|
| 305 | /// ```
|
| 306 | ///
|
| 307 | /// This error can be returned only for the last attribute in the list,
|
| 308 | /// because all input was consumed during scanning for a quote.
|
| 309 | ExpectedQuote(usize, u8),
|
| 310 | /// An attribute with the same name was already encountered. Two parameters
|
| 311 | /// define (1) the error position relative to the start of the owning tag
|
| 312 | /// for a new attribute and (2) the start position of a previously encountered
|
| 313 | /// attribute with the same name.
|
| 314 | ///
|
| 315 | /// Example of input that raises this error:
|
| 316 | ///
|
| 317 | /// ```xml
|
| 318 | /// <tag key = 'value' key="value2" attr3='value3' />
|
| 319 | /// <!-- ^ ^ ^~~ recovery position (32) -->
|
| 320 | /// <!-- | '~~ error position (19) -->
|
| 321 | /// <!-- '~~ previous position (4) -->
|
| 322 | /// ```
|
| 323 | ///
|
| 324 | /// This error is returned only when [`Attributes::with_checks()`] is set
|
| 325 | /// to `true` (that is default behavior).
|
| 326 | Duplicated(usize, usize),
|
| 327 | }
|
| 328 |
|
| 329 | impl Display for AttrError {
|
| 330 | fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
| 331 | match self {
|
| 332 | Self::ExpectedEq(pos) => write!(
|
| 333 | f,
|
| 334 | r#"position {}: attribute key must be directly followed by `=` or space"# ,
|
| 335 | pos
|
| 336 | ),
|
| 337 | Self::ExpectedValue(pos) => write!(
|
| 338 | f,
|
| 339 | r#"position {}: `=` must be followed by an attribute value"# ,
|
| 340 | pos
|
| 341 | ),
|
| 342 | Self::UnquotedValue(pos) => write!(
|
| 343 | f,
|
| 344 | r#"position {}: attribute value must be enclosed in `"` or `'`"# ,
|
| 345 | pos
|
| 346 | ),
|
| 347 | Self::ExpectedQuote(pos, quote) => write!(
|
| 348 | f,
|
| 349 | r#"position {}: missing closing quote ` {}` in attribute value"# ,
|
| 350 | pos, *quote as char
|
| 351 | ),
|
| 352 | Self::Duplicated(pos1, pos2) => write!(
|
| 353 | f,
|
| 354 | r#"position {}: duplicated attribute, previous declaration at position {}"# ,
|
| 355 | pos1, pos2
|
| 356 | ),
|
| 357 | }
|
| 358 | }
|
| 359 | }
|
| 360 |
|
| 361 | impl std::error::Error for AttrError {}
|
| 362 |
|
| 363 | ////////////////////////////////////////////////////////////////////////////////////////////////////
|
| 364 |
|
| 365 | /// A struct representing a key/value XML or HTML [attribute].
|
| 366 | ///
|
| 367 | /// [attribute]: https://www.w3.org/TR/xml11/#NT-Attribute
|
| 368 | #[derive (Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
| 369 | pub enum Attr<T> {
|
| 370 | /// Attribute with value enclosed in double quotes (`"`). Attribute key and
|
| 371 | /// value provided. This is a canonical XML-style attribute.
|
| 372 | DoubleQ(T, T),
|
| 373 | /// Attribute with value enclosed in single quotes (`'`). Attribute key and
|
| 374 | /// value provided. This is an XML-style attribute.
|
| 375 | SingleQ(T, T),
|
| 376 | /// Attribute with value not enclosed in quotes. Attribute key and value
|
| 377 | /// provided. This is HTML-style attribute, it can be returned in HTML-mode
|
| 378 | /// parsing only. In an XML mode [`AttrError::UnquotedValue`] will be raised
|
| 379 | /// instead.
|
| 380 | ///
|
| 381 | /// Attribute value can be invalid according to the [HTML specification],
|
| 382 | /// in particular, it can contain `"`, `'`, `=`, `<`, and <code>`</code>
|
| 383 | /// characters. The absence of the `>` character is nevertheless guaranteed,
|
| 384 | /// since the parser extracts [events] based on them even before the start
|
| 385 | /// of parsing attributes.
|
| 386 | ///
|
| 387 | /// [HTML specification]: https://html.spec.whatwg.org/#unquoted
|
| 388 | /// [events]: crate::events::Event::Start
|
| 389 | Unquoted(T, T),
|
| 390 | /// Attribute without value. Attribute key provided. This is HTML-style attribute,
|
| 391 | /// it can be returned in HTML-mode parsing only. In XML mode
|
| 392 | /// [`AttrError::ExpectedEq`] will be raised instead.
|
| 393 | Empty(T),
|
| 394 | }
|
| 395 |
|
| 396 | impl<T> Attr<T> {
|
| 397 | /// Maps an `Attr<T>` to `Attr<U>` by applying a function to a contained key and value.
|
| 398 | #[inline ]
|
| 399 | pub fn map<U, F>(self, mut f: F) -> Attr<U>
|
| 400 | where
|
| 401 | F: FnMut(T) -> U,
|
| 402 | {
|
| 403 | match self {
|
| 404 | Attr::DoubleQ(key: T, value: T) => Attr::DoubleQ(f(key), f(value)),
|
| 405 | Attr::SingleQ(key: T, value: T) => Attr::SingleQ(f(key), f(value)),
|
| 406 | Attr::Empty(key: T) => Attr::Empty(f(key)),
|
| 407 | Attr::Unquoted(key: T, value: T) => Attr::Unquoted(f(key), f(value)),
|
| 408 | }
|
| 409 | }
|
| 410 | }
|
| 411 |
|
| 412 | impl<'a> Attr<&'a [u8]> {
|
| 413 | /// Returns the key value
|
| 414 | #[inline ]
|
| 415 | pub fn key(&self) -> QName<'a> {
|
| 416 | QName(match self {
|
| 417 | Attr::DoubleQ(key, _) => key,
|
| 418 | Attr::SingleQ(key, _) => key,
|
| 419 | Attr::Empty(key) => key,
|
| 420 | Attr::Unquoted(key, _) => key,
|
| 421 | })
|
| 422 | }
|
| 423 | /// Returns the attribute value. For [`Self::Empty`] variant an empty slice
|
| 424 | /// is returned according to the [HTML specification].
|
| 425 | ///
|
| 426 | /// [HTML specification]: https://www.w3.org/TR/2012/WD-html-markup-20120329/syntax.html#syntax-attr-empty
|
| 427 | #[inline ]
|
| 428 | pub fn value(&self) -> &'a [u8] {
|
| 429 | match self {
|
| 430 | Attr::DoubleQ(_, value) => value,
|
| 431 | Attr::SingleQ(_, value) => value,
|
| 432 | Attr::Empty(_) => &[],
|
| 433 | Attr::Unquoted(_, value) => value,
|
| 434 | }
|
| 435 | }
|
| 436 | }
|
| 437 |
|
| 438 | impl<T: AsRef<[u8]>> Debug for Attr<T> {
|
| 439 | fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
| 440 | match self {
|
| 441 | Attr::DoubleQ(key, value) => f
|
| 442 | .debug_tuple("Attr::DoubleQ" )
|
| 443 | .field(&Bytes(key.as_ref()))
|
| 444 | .field(&Bytes(value.as_ref()))
|
| 445 | .finish(),
|
| 446 | Attr::SingleQ(key, value) => f
|
| 447 | .debug_tuple("Attr::SingleQ" )
|
| 448 | .field(&Bytes(key.as_ref()))
|
| 449 | .field(&Bytes(value.as_ref()))
|
| 450 | .finish(),
|
| 451 | Attr::Empty(key) => f
|
| 452 | .debug_tuple("Attr::Empty" )
|
| 453 | // Comment to prevent formatting and keep style consistent
|
| 454 | .field(&Bytes(key.as_ref()))
|
| 455 | .finish(),
|
| 456 | Attr::Unquoted(key, value) => f
|
| 457 | .debug_tuple("Attr::Unquoted" )
|
| 458 | .field(&Bytes(key.as_ref()))
|
| 459 | .field(&Bytes(value.as_ref()))
|
| 460 | .finish(),
|
| 461 | }
|
| 462 | }
|
| 463 | }
|
| 464 |
|
| 465 | /// Unpacks attribute key and value into tuple of this two elements.
|
| 466 | /// `None` value element is returned only for [`Attr::Empty`] variant.
|
| 467 | impl<T> From<Attr<T>> for (T, Option<T>) {
|
| 468 | #[inline ]
|
| 469 | fn from(attr: Attr<T>) -> Self {
|
| 470 | match attr {
|
| 471 | Attr::DoubleQ(key: T, value: T) => (key, Some(value)),
|
| 472 | Attr::SingleQ(key: T, value: T) => (key, Some(value)),
|
| 473 | Attr::Empty(key: T) => (key, None),
|
| 474 | Attr::Unquoted(key: T, value: T) => (key, Some(value)),
|
| 475 | }
|
| 476 | }
|
| 477 | }
|
| 478 |
|
| 479 | ////////////////////////////////////////////////////////////////////////////////////////////////////
|
| 480 |
|
| 481 | type AttrResult = Result<Attr<Range<usize>>, AttrError>;
|
| 482 |
|
| 483 | #[derive (Clone, Copy, Debug)]
|
| 484 | enum State {
|
| 485 | /// Iteration finished, iterator will return `None` to all [`IterState::next`]
|
| 486 | /// requests.
|
| 487 | Done,
|
| 488 | /// The last attribute returned was deserialized successfully. Contains an
|
| 489 | /// offset from which next attribute should be searched.
|
| 490 | Next(usize),
|
| 491 | /// The last attribute returns [`AttrError::UnquotedValue`], offset pointed
|
| 492 | /// to the beginning of the value. Recover should skip a value
|
| 493 | SkipValue(usize),
|
| 494 | /// The last attribute returns [`AttrError::Duplicated`], offset pointed to
|
| 495 | /// the equal (`=`) sign. Recover should skip it and a value
|
| 496 | SkipEqValue(usize),
|
| 497 | }
|
| 498 |
|
| 499 | /// External iterator over spans of attribute key and value
|
| 500 | #[derive (Clone, Debug)]
|
| 501 | pub(crate) struct IterState {
|
| 502 | /// Iteration state that determines what actions should be done before the
|
| 503 | /// actual parsing of the next attribute
|
| 504 | state: State,
|
| 505 | /// If `true`, enables ability to parse unquoted values and key-only (empty)
|
| 506 | /// attributes
|
| 507 | html: bool,
|
| 508 | /// If `true`, checks for duplicate names
|
| 509 | check_duplicates: bool,
|
| 510 | /// If `check_duplicates` is set, contains the ranges of already parsed attribute
|
| 511 | /// names. We store a ranges instead of slices to able to report a previous
|
| 512 | /// attribute position
|
| 513 | keys: Vec<Range<usize>>,
|
| 514 | }
|
| 515 |
|
| 516 | impl IterState {
|
| 517 | pub fn new(offset: usize, html: bool) -> Self {
|
| 518 | Self {
|
| 519 | state: State::Next(offset),
|
| 520 | html,
|
| 521 | check_duplicates: true,
|
| 522 | keys: Vec::new(),
|
| 523 | }
|
| 524 | }
|
| 525 |
|
| 526 | /// Recover from an error that could have been made on a previous step.
|
| 527 | /// Returns an offset from which parsing should continue.
|
| 528 | /// If there no input left, returns `None`.
|
| 529 | fn recover(&self, slice: &[u8]) -> Option<usize> {
|
| 530 | match self.state {
|
| 531 | State::Done => None,
|
| 532 | State::Next(offset) => Some(offset),
|
| 533 | State::SkipValue(offset) => self.skip_value(slice, offset),
|
| 534 | State::SkipEqValue(offset) => self.skip_eq_value(slice, offset),
|
| 535 | }
|
| 536 | }
|
| 537 |
|
| 538 | /// Skip all characters up to first space symbol or end-of-input
|
| 539 | #[inline ]
|
| 540 | #[allow (clippy::manual_map)]
|
| 541 | fn skip_value(&self, slice: &[u8], offset: usize) -> Option<usize> {
|
| 542 | let mut iter = (offset..).zip(slice[offset..].iter());
|
| 543 |
|
| 544 | match iter.find(|(_, &b)| is_whitespace(b)) {
|
| 545 | // Input: ` key = value `
|
| 546 | // | ^
|
| 547 | // offset e
|
| 548 | Some((e, _)) => Some(e),
|
| 549 | // Input: ` key = value`
|
| 550 | // | ^
|
| 551 | // offset e = len()
|
| 552 | None => None,
|
| 553 | }
|
| 554 | }
|
| 555 |
|
| 556 | /// Skip all characters up to first space symbol or end-of-input
|
| 557 | #[inline ]
|
| 558 | fn skip_eq_value(&self, slice: &[u8], offset: usize) -> Option<usize> {
|
| 559 | let mut iter = (offset..).zip(slice[offset..].iter());
|
| 560 |
|
| 561 | // Skip all up to the quote and get the quote type
|
| 562 | let quote = match iter.find(|(_, &b)| !is_whitespace(b)) {
|
| 563 | // Input: ` key = "`
|
| 564 | // | ^
|
| 565 | // offset
|
| 566 | Some((_, b'"' )) => b'"' ,
|
| 567 | // Input: ` key = '`
|
| 568 | // | ^
|
| 569 | // offset
|
| 570 | Some((_, b' \'' )) => b' \'' ,
|
| 571 |
|
| 572 | // Input: ` key = x`
|
| 573 | // | ^
|
| 574 | // offset
|
| 575 | Some((offset, _)) => return self.skip_value(slice, offset),
|
| 576 | // Input: ` key = `
|
| 577 | // | ^
|
| 578 | // offset
|
| 579 | None => return None,
|
| 580 | };
|
| 581 |
|
| 582 | match iter.find(|(_, &b)| b == quote) {
|
| 583 | // Input: ` key = " "`
|
| 584 | // ^
|
| 585 | Some((e, b'"' )) => Some(e),
|
| 586 | // Input: ` key = ' '`
|
| 587 | // ^
|
| 588 | Some((e, _)) => Some(e),
|
| 589 |
|
| 590 | // Input: ` key = " `
|
| 591 | // Input: ` key = ' `
|
| 592 | // ^
|
| 593 | // Closing quote not found
|
| 594 | None => None,
|
| 595 | }
|
| 596 | }
|
| 597 |
|
| 598 | #[inline ]
|
| 599 | fn check_for_duplicates(
|
| 600 | &mut self,
|
| 601 | slice: &[u8],
|
| 602 | key: Range<usize>,
|
| 603 | ) -> Result<Range<usize>, AttrError> {
|
| 604 | if self.check_duplicates {
|
| 605 | if let Some(prev) = self
|
| 606 | .keys
|
| 607 | .iter()
|
| 608 | .find(|r| slice[(*r).clone()] == slice[key.clone()])
|
| 609 | {
|
| 610 | return Err(AttrError::Duplicated(key.start, prev.start));
|
| 611 | }
|
| 612 | self.keys.push(key.clone());
|
| 613 | }
|
| 614 | Ok(key)
|
| 615 | }
|
| 616 |
|
| 617 | /// # Parameters
|
| 618 | ///
|
| 619 | /// - `slice`: content of the tag, used for checking for duplicates
|
| 620 | /// - `key`: Range of key in slice, if iterator in HTML mode
|
| 621 | /// - `offset`: Position of error if iterator in XML mode
|
| 622 | #[inline ]
|
| 623 | fn key_only(&mut self, slice: &[u8], key: Range<usize>, offset: usize) -> Option<AttrResult> {
|
| 624 | Some(if self.html {
|
| 625 | self.check_for_duplicates(slice, key).map(Attr::Empty)
|
| 626 | } else {
|
| 627 | Err(AttrError::ExpectedEq(offset))
|
| 628 | })
|
| 629 | }
|
| 630 |
|
| 631 | #[inline ]
|
| 632 | fn double_q(&mut self, key: Range<usize>, value: Range<usize>) -> Option<AttrResult> {
|
| 633 | self.state = State::Next(value.end + 1); // +1 for `"`
|
| 634 |
|
| 635 | Some(Ok(Attr::DoubleQ(key, value)))
|
| 636 | }
|
| 637 |
|
| 638 | #[inline ]
|
| 639 | fn single_q(&mut self, key: Range<usize>, value: Range<usize>) -> Option<AttrResult> {
|
| 640 | self.state = State::Next(value.end + 1); // +1 for `'`
|
| 641 |
|
| 642 | Some(Ok(Attr::SingleQ(key, value)))
|
| 643 | }
|
| 644 |
|
| 645 | pub fn next(&mut self, slice: &[u8]) -> Option<AttrResult> {
|
| 646 | let mut iter = match self.recover(slice) {
|
| 647 | Some(offset) => (offset..).zip(slice[offset..].iter()),
|
| 648 | None => return None,
|
| 649 | };
|
| 650 |
|
| 651 | // Index where next key started
|
| 652 | let start_key = match iter.find(|(_, &b)| !is_whitespace(b)) {
|
| 653 | // Input: ` key`
|
| 654 | // ^
|
| 655 | Some((s, _)) => s,
|
| 656 | // Input: ` `
|
| 657 | // ^
|
| 658 | None => {
|
| 659 | // Because we reach end-of-input, stop iteration on next call
|
| 660 | self.state = State::Done;
|
| 661 | return None;
|
| 662 | }
|
| 663 | };
|
| 664 | // Span of a key
|
| 665 | let (key, offset) = match iter.find(|(_, &b)| b == b'=' || is_whitespace(b)) {
|
| 666 | // Input: ` key=`
|
| 667 | // | ^
|
| 668 | // s e
|
| 669 | Some((e, b'=' )) => (start_key..e, e),
|
| 670 |
|
| 671 | // Input: ` key `
|
| 672 | // ^
|
| 673 | Some((e, _)) => match iter.find(|(_, &b)| !is_whitespace(b)) {
|
| 674 | // Input: ` key =`
|
| 675 | // | | ^
|
| 676 | // start_key e
|
| 677 | Some((offset, b'=' )) => (start_key..e, offset),
|
| 678 | // Input: ` key x`
|
| 679 | // | | ^
|
| 680 | // start_key e
|
| 681 | // If HTML-like attributes is allowed, this is the result, otherwise error
|
| 682 | Some((offset, _)) => {
|
| 683 | // In any case, recovering is not required
|
| 684 | self.state = State::Next(offset);
|
| 685 | return self.key_only(slice, start_key..e, offset);
|
| 686 | }
|
| 687 | // Input: ` key `
|
| 688 | // | | ^
|
| 689 | // start_key e
|
| 690 | // If HTML-like attributes is allowed, this is the result, otherwise error
|
| 691 | None => {
|
| 692 | // Because we reach end-of-input, stop iteration on next call
|
| 693 | self.state = State::Done;
|
| 694 | return self.key_only(slice, start_key..e, slice.len());
|
| 695 | }
|
| 696 | },
|
| 697 |
|
| 698 | // Input: ` key`
|
| 699 | // | ^
|
| 700 | // s e = len()
|
| 701 | // If HTML-like attributes is allowed, this is the result, otherwise error
|
| 702 | None => {
|
| 703 | // Because we reach end-of-input, stop iteration on next call
|
| 704 | self.state = State::Done;
|
| 705 | let e = slice.len();
|
| 706 | return self.key_only(slice, start_key..e, e);
|
| 707 | }
|
| 708 | };
|
| 709 |
|
| 710 | let key = match self.check_for_duplicates(slice, key) {
|
| 711 | Err(e) => {
|
| 712 | self.state = State::SkipEqValue(offset);
|
| 713 | return Some(Err(e));
|
| 714 | }
|
| 715 | Ok(key) => key,
|
| 716 | };
|
| 717 |
|
| 718 | ////////////////////////////////////////////////////////////////////////
|
| 719 |
|
| 720 | // Gets the position of quote and quote type
|
| 721 | let (start_value, quote) = match iter.find(|(_, &b)| !is_whitespace(b)) {
|
| 722 | // Input: ` key = "`
|
| 723 | // ^
|
| 724 | Some((s, b'"' )) => (s + 1, b'"' ),
|
| 725 | // Input: ` key = '`
|
| 726 | // ^
|
| 727 | Some((s, b' \'' )) => (s + 1, b' \'' ),
|
| 728 |
|
| 729 | // Input: ` key = x`
|
| 730 | // ^
|
| 731 | // If HTML-like attributes is allowed, this is the start of the value
|
| 732 | Some((s, _)) if self.html => {
|
| 733 | // We do not check validity of attribute value characters as required
|
| 734 | // according to https://html.spec.whatwg.org/#unquoted. It can be done
|
| 735 | // during validation phase
|
| 736 | let end = match iter.find(|(_, &b)| is_whitespace(b)) {
|
| 737 | // Input: ` key = value `
|
| 738 | // | ^
|
| 739 | // s e
|
| 740 | Some((e, _)) => e,
|
| 741 | // Input: ` key = value`
|
| 742 | // | ^
|
| 743 | // s e = len()
|
| 744 | None => slice.len(),
|
| 745 | };
|
| 746 | self.state = State::Next(end);
|
| 747 | return Some(Ok(Attr::Unquoted(key, s..end)));
|
| 748 | }
|
| 749 | // Input: ` key = x`
|
| 750 | // ^
|
| 751 | Some((s, _)) => {
|
| 752 | self.state = State::SkipValue(s);
|
| 753 | return Some(Err(AttrError::UnquotedValue(s)));
|
| 754 | }
|
| 755 |
|
| 756 | // Input: ` key = `
|
| 757 | // ^
|
| 758 | None => {
|
| 759 | // Because we reach end-of-input, stop iteration on next call
|
| 760 | self.state = State::Done;
|
| 761 | return Some(Err(AttrError::ExpectedValue(slice.len())));
|
| 762 | }
|
| 763 | };
|
| 764 |
|
| 765 | match iter.find(|(_, &b)| b == quote) {
|
| 766 | // Input: ` key = " "`
|
| 767 | // ^
|
| 768 | Some((e, b'"' )) => self.double_q(key, start_value..e),
|
| 769 | // Input: ` key = ' '`
|
| 770 | // ^
|
| 771 | Some((e, _)) => self.single_q(key, start_value..e),
|
| 772 |
|
| 773 | // Input: ` key = " `
|
| 774 | // Input: ` key = ' `
|
| 775 | // ^
|
| 776 | // Closing quote not found
|
| 777 | None => {
|
| 778 | // Because we reach end-of-input, stop iteration on next call
|
| 779 | self.state = State::Done;
|
| 780 | Some(Err(AttrError::ExpectedQuote(slice.len(), quote)))
|
| 781 | }
|
| 782 | }
|
| 783 | }
|
| 784 | }
|
| 785 |
|
| 786 | ////////////////////////////////////////////////////////////////////////////////////////////////////
|
| 787 |
|
| 788 | /// Checks, how parsing of XML-style attributes works. Each attribute should
|
| 789 | /// have a value, enclosed in single or double quotes.
|
| 790 | #[cfg (test)]
|
| 791 | mod xml {
|
| 792 | use super::*;
|
| 793 | use pretty_assertions::assert_eq;
|
| 794 |
|
| 795 | /// Checked attribute is the single attribute
|
| 796 | mod single {
|
| 797 | use super::*;
|
| 798 | use pretty_assertions::assert_eq;
|
| 799 |
|
| 800 | /// Attribute have a value enclosed in single quotes
|
| 801 | #[test ]
|
| 802 | fn single_quoted() {
|
| 803 | let mut iter = Attributes::new(r#"tag key='value'"# , 3);
|
| 804 |
|
| 805 | assert_eq!(
|
| 806 | iter.next(),
|
| 807 | Some(Ok(Attribute {
|
| 808 | key: QName(b"key" ),
|
| 809 | value: Cow::Borrowed(b"value" ),
|
| 810 | }))
|
| 811 | );
|
| 812 | assert_eq!(iter.next(), None);
|
| 813 | assert_eq!(iter.next(), None);
|
| 814 | }
|
| 815 |
|
| 816 | /// Attribute have a value enclosed in double quotes
|
| 817 | #[test ]
|
| 818 | fn double_quoted() {
|
| 819 | let mut iter = Attributes::new(r#"tag key="value""# , 3);
|
| 820 |
|
| 821 | assert_eq!(
|
| 822 | iter.next(),
|
| 823 | Some(Ok(Attribute {
|
| 824 | key: QName(b"key" ),
|
| 825 | value: Cow::Borrowed(b"value" ),
|
| 826 | }))
|
| 827 | );
|
| 828 | assert_eq!(iter.next(), None);
|
| 829 | assert_eq!(iter.next(), None);
|
| 830 | }
|
| 831 |
|
| 832 | /// Attribute have a value, not enclosed in quotes
|
| 833 | #[test ]
|
| 834 | fn unquoted() {
|
| 835 | let mut iter = Attributes::new(r#"tag key=value"# , 3);
|
| 836 | // 0 ^ = 8
|
| 837 |
|
| 838 | assert_eq!(iter.next(), Some(Err(AttrError::UnquotedValue(8))));
|
| 839 | assert_eq!(iter.next(), None);
|
| 840 | assert_eq!(iter.next(), None);
|
| 841 | }
|
| 842 |
|
| 843 | /// Only attribute key is present
|
| 844 | #[test ]
|
| 845 | fn key_only() {
|
| 846 | let mut iter = Attributes::new(r#"tag key"# , 3);
|
| 847 | // 0 ^ = 7
|
| 848 |
|
| 849 | assert_eq!(iter.next(), Some(Err(AttrError::ExpectedEq(7))));
|
| 850 | assert_eq!(iter.next(), None);
|
| 851 | assert_eq!(iter.next(), None);
|
| 852 | }
|
| 853 |
|
| 854 | /// Key is started with an invalid symbol (a single quote in this test).
|
| 855 | /// Because we do not check validity of keys and values during parsing,
|
| 856 | /// that invalid attribute will be returned
|
| 857 | #[test ]
|
| 858 | fn key_start_invalid() {
|
| 859 | let mut iter = Attributes::new(r#"tag 'key'='value'"# , 3);
|
| 860 |
|
| 861 | assert_eq!(
|
| 862 | iter.next(),
|
| 863 | Some(Ok(Attribute {
|
| 864 | key: QName(b"'key'" ),
|
| 865 | value: Cow::Borrowed(b"value" ),
|
| 866 | }))
|
| 867 | );
|
| 868 | assert_eq!(iter.next(), None);
|
| 869 | assert_eq!(iter.next(), None);
|
| 870 | }
|
| 871 |
|
| 872 | /// Key contains an invalid symbol (an ampersand in this test).
|
| 873 | /// Because we do not check validity of keys and values during parsing,
|
| 874 | /// that invalid attribute will be returned
|
| 875 | #[test ]
|
| 876 | fn key_contains_invalid() {
|
| 877 | let mut iter = Attributes::new(r#"tag key&jey='value'"# , 3);
|
| 878 |
|
| 879 | assert_eq!(
|
| 880 | iter.next(),
|
| 881 | Some(Ok(Attribute {
|
| 882 | key: QName(b"key&jey" ),
|
| 883 | value: Cow::Borrowed(b"value" ),
|
| 884 | }))
|
| 885 | );
|
| 886 | assert_eq!(iter.next(), None);
|
| 887 | assert_eq!(iter.next(), None);
|
| 888 | }
|
| 889 |
|
| 890 | /// Attribute value is missing after `=`
|
| 891 | #[test ]
|
| 892 | fn missed_value() {
|
| 893 | let mut iter = Attributes::new(r#"tag key="# , 3);
|
| 894 | // 0 ^ = 8
|
| 895 |
|
| 896 | assert_eq!(iter.next(), Some(Err(AttrError::ExpectedValue(8))));
|
| 897 | assert_eq!(iter.next(), None);
|
| 898 | assert_eq!(iter.next(), None);
|
| 899 | }
|
| 900 | }
|
| 901 |
|
| 902 | /// Checked attribute is the first attribute in the list of many attributes
|
| 903 | mod first {
|
| 904 | use super::*;
|
| 905 | use pretty_assertions::assert_eq;
|
| 906 |
|
| 907 | /// Attribute have a value enclosed in single quotes
|
| 908 | #[test ]
|
| 909 | fn single_quoted() {
|
| 910 | let mut iter = Attributes::new(r#"tag key='value' regular='attribute'"# , 3);
|
| 911 |
|
| 912 | assert_eq!(
|
| 913 | iter.next(),
|
| 914 | Some(Ok(Attribute {
|
| 915 | key: QName(b"key" ),
|
| 916 | value: Cow::Borrowed(b"value" ),
|
| 917 | }))
|
| 918 | );
|
| 919 | assert_eq!(
|
| 920 | iter.next(),
|
| 921 | Some(Ok(Attribute {
|
| 922 | key: QName(b"regular" ),
|
| 923 | value: Cow::Borrowed(b"attribute" ),
|
| 924 | }))
|
| 925 | );
|
| 926 | assert_eq!(iter.next(), None);
|
| 927 | assert_eq!(iter.next(), None);
|
| 928 | }
|
| 929 |
|
| 930 | /// Attribute have a value enclosed in double quotes
|
| 931 | #[test ]
|
| 932 | fn double_quoted() {
|
| 933 | let mut iter = Attributes::new(r#"tag key="value" regular='attribute'"# , 3);
|
| 934 |
|
| 935 | assert_eq!(
|
| 936 | iter.next(),
|
| 937 | Some(Ok(Attribute {
|
| 938 | key: QName(b"key" ),
|
| 939 | value: Cow::Borrowed(b"value" ),
|
| 940 | }))
|
| 941 | );
|
| 942 | assert_eq!(
|
| 943 | iter.next(),
|
| 944 | Some(Ok(Attribute {
|
| 945 | key: QName(b"regular" ),
|
| 946 | value: Cow::Borrowed(b"attribute" ),
|
| 947 | }))
|
| 948 | );
|
| 949 | assert_eq!(iter.next(), None);
|
| 950 | assert_eq!(iter.next(), None);
|
| 951 | }
|
| 952 |
|
| 953 | /// Attribute have a value, not enclosed in quotes
|
| 954 | #[test ]
|
| 955 | fn unquoted() {
|
| 956 | let mut iter = Attributes::new(r#"tag key=value regular='attribute'"# , 3);
|
| 957 | // 0 ^ = 8
|
| 958 |
|
| 959 | assert_eq!(iter.next(), Some(Err(AttrError::UnquotedValue(8))));
|
| 960 | // check error recovery
|
| 961 | assert_eq!(
|
| 962 | iter.next(),
|
| 963 | Some(Ok(Attribute {
|
| 964 | key: QName(b"regular" ),
|
| 965 | value: Cow::Borrowed(b"attribute" ),
|
| 966 | }))
|
| 967 | );
|
| 968 | assert_eq!(iter.next(), None);
|
| 969 | assert_eq!(iter.next(), None);
|
| 970 | }
|
| 971 |
|
| 972 | /// Only attribute key is present
|
| 973 | #[test ]
|
| 974 | fn key_only() {
|
| 975 | let mut iter = Attributes::new(r#"tag key regular='attribute'"# , 3);
|
| 976 | // 0 ^ = 8
|
| 977 |
|
| 978 | assert_eq!(iter.next(), Some(Err(AttrError::ExpectedEq(8))));
|
| 979 | // check error recovery
|
| 980 | assert_eq!(
|
| 981 | iter.next(),
|
| 982 | Some(Ok(Attribute {
|
| 983 | key: QName(b"regular" ),
|
| 984 | value: Cow::Borrowed(b"attribute" ),
|
| 985 | }))
|
| 986 | );
|
| 987 | assert_eq!(iter.next(), None);
|
| 988 | assert_eq!(iter.next(), None);
|
| 989 | }
|
| 990 |
|
| 991 | /// Key is started with an invalid symbol (a single quote in this test).
|
| 992 | /// Because we do not check validity of keys and values during parsing,
|
| 993 | /// that invalid attribute will be returned
|
| 994 | #[test ]
|
| 995 | fn key_start_invalid() {
|
| 996 | let mut iter = Attributes::new(r#"tag 'key'='value' regular='attribute'"# , 3);
|
| 997 |
|
| 998 | assert_eq!(
|
| 999 | iter.next(),
|
| 1000 | Some(Ok(Attribute {
|
| 1001 | key: QName(b"'key'" ),
|
| 1002 | value: Cow::Borrowed(b"value" ),
|
| 1003 | }))
|
| 1004 | );
|
| 1005 | assert_eq!(
|
| 1006 | iter.next(),
|
| 1007 | Some(Ok(Attribute {
|
| 1008 | key: QName(b"regular" ),
|
| 1009 | value: Cow::Borrowed(b"attribute" ),
|
| 1010 | }))
|
| 1011 | );
|
| 1012 | assert_eq!(iter.next(), None);
|
| 1013 | assert_eq!(iter.next(), None);
|
| 1014 | }
|
| 1015 |
|
| 1016 | /// Key contains an invalid symbol (an ampersand in this test).
|
| 1017 | /// Because we do not check validity of keys and values during parsing,
|
| 1018 | /// that invalid attribute will be returned
|
| 1019 | #[test ]
|
| 1020 | fn key_contains_invalid() {
|
| 1021 | let mut iter = Attributes::new(r#"tag key&jey='value' regular='attribute'"# , 3);
|
| 1022 |
|
| 1023 | assert_eq!(
|
| 1024 | iter.next(),
|
| 1025 | Some(Ok(Attribute {
|
| 1026 | key: QName(b"key&jey" ),
|
| 1027 | value: Cow::Borrowed(b"value" ),
|
| 1028 | }))
|
| 1029 | );
|
| 1030 | assert_eq!(
|
| 1031 | iter.next(),
|
| 1032 | Some(Ok(Attribute {
|
| 1033 | key: QName(b"regular" ),
|
| 1034 | value: Cow::Borrowed(b"attribute" ),
|
| 1035 | }))
|
| 1036 | );
|
| 1037 | assert_eq!(iter.next(), None);
|
| 1038 | assert_eq!(iter.next(), None);
|
| 1039 | }
|
| 1040 |
|
| 1041 | /// Attribute value is missing after `=`.
|
| 1042 | #[test ]
|
| 1043 | fn missed_value() {
|
| 1044 | let mut iter = Attributes::new(r#"tag key= regular='attribute'"# , 3);
|
| 1045 | // 0 ^ = 9
|
| 1046 |
|
| 1047 | assert_eq!(iter.next(), Some(Err(AttrError::UnquotedValue(9))));
|
| 1048 | // Because we do not check validity of keys and values during parsing,
|
| 1049 | // "error='recovery'" is considered, as unquoted attribute value and
|
| 1050 | // skipped during recovery and iteration finished
|
| 1051 | assert_eq!(iter.next(), None);
|
| 1052 | assert_eq!(iter.next(), None);
|
| 1053 |
|
| 1054 | ////////////////////////////////////////////////////////////////////
|
| 1055 |
|
| 1056 | let mut iter = Attributes::new(r#"tag key= regular= 'attribute'"# , 3);
|
| 1057 | // 0 ^ = 9 ^ = 29
|
| 1058 |
|
| 1059 | // In that case "regular=" considered as unquoted value
|
| 1060 | assert_eq!(iter.next(), Some(Err(AttrError::UnquotedValue(9))));
|
| 1061 | // In that case "'attribute'" considered as a key, because we do not check
|
| 1062 | // validity of key names
|
| 1063 | assert_eq!(iter.next(), Some(Err(AttrError::ExpectedEq(29))));
|
| 1064 | assert_eq!(iter.next(), None);
|
| 1065 | assert_eq!(iter.next(), None);
|
| 1066 |
|
| 1067 | ////////////////////////////////////////////////////////////////////
|
| 1068 |
|
| 1069 | let mut iter = Attributes::new(r#"tag key= regular ='attribute'"# , 3);
|
| 1070 | // 0 ^ = 9 ^ = 29
|
| 1071 |
|
| 1072 | // In that case "regular" considered as unquoted value
|
| 1073 | assert_eq!(iter.next(), Some(Err(AttrError::UnquotedValue(9))));
|
| 1074 | // In that case "='attribute'" considered as a key, because we do not check
|
| 1075 | // validity of key names
|
| 1076 | assert_eq!(iter.next(), Some(Err(AttrError::ExpectedEq(29))));
|
| 1077 | assert_eq!(iter.next(), None);
|
| 1078 | assert_eq!(iter.next(), None);
|
| 1079 |
|
| 1080 | ////////////////////////////////////////////////////////////////////
|
| 1081 |
|
| 1082 | let mut iter = Attributes::new(r#"tag key= regular = 'attribute'"# , 3);
|
| 1083 | // 0 ^ = 9 ^ = 19 ^ = 30
|
| 1084 |
|
| 1085 | assert_eq!(iter.next(), Some(Err(AttrError::UnquotedValue(9))));
|
| 1086 | // In that case second "=" considered as a key, because we do not check
|
| 1087 | // validity of key names
|
| 1088 | assert_eq!(iter.next(), Some(Err(AttrError::ExpectedEq(19))));
|
| 1089 | // In that case "'attribute'" considered as a key, because we do not check
|
| 1090 | // validity of key names
|
| 1091 | assert_eq!(iter.next(), Some(Err(AttrError::ExpectedEq(30))));
|
| 1092 | assert_eq!(iter.next(), None);
|
| 1093 | assert_eq!(iter.next(), None);
|
| 1094 | }
|
| 1095 | }
|
| 1096 |
|
| 1097 | /// Copy of single, but with additional spaces in markup
|
| 1098 | mod sparsed {
|
| 1099 | use super::*;
|
| 1100 | use pretty_assertions::assert_eq;
|
| 1101 |
|
| 1102 | /// Attribute have a value enclosed in single quotes
|
| 1103 | #[test ]
|
| 1104 | fn single_quoted() {
|
| 1105 | let mut iter = Attributes::new(r#"tag key = 'value' "# , 3);
|
| 1106 |
|
| 1107 | assert_eq!(
|
| 1108 | iter.next(),
|
| 1109 | Some(Ok(Attribute {
|
| 1110 | key: QName(b"key" ),
|
| 1111 | value: Cow::Borrowed(b"value" ),
|
| 1112 | }))
|
| 1113 | );
|
| 1114 | assert_eq!(iter.next(), None);
|
| 1115 | assert_eq!(iter.next(), None);
|
| 1116 | }
|
| 1117 |
|
| 1118 | /// Attribute have a value enclosed in double quotes
|
| 1119 | #[test ]
|
| 1120 | fn double_quoted() {
|
| 1121 | let mut iter = Attributes::new(r#"tag key = "value" "# , 3);
|
| 1122 |
|
| 1123 | assert_eq!(
|
| 1124 | iter.next(),
|
| 1125 | Some(Ok(Attribute {
|
| 1126 | key: QName(b"key" ),
|
| 1127 | value: Cow::Borrowed(b"value" ),
|
| 1128 | }))
|
| 1129 | );
|
| 1130 | assert_eq!(iter.next(), None);
|
| 1131 | assert_eq!(iter.next(), None);
|
| 1132 | }
|
| 1133 |
|
| 1134 | /// Attribute have a value, not enclosed in quotes
|
| 1135 | #[test ]
|
| 1136 | fn unquoted() {
|
| 1137 | let mut iter = Attributes::new(r#"tag key = value "# , 3);
|
| 1138 | // 0 ^ = 10
|
| 1139 |
|
| 1140 | assert_eq!(iter.next(), Some(Err(AttrError::UnquotedValue(10))));
|
| 1141 | assert_eq!(iter.next(), None);
|
| 1142 | assert_eq!(iter.next(), None);
|
| 1143 | }
|
| 1144 |
|
| 1145 | /// Only attribute key is present
|
| 1146 | #[test ]
|
| 1147 | fn key_only() {
|
| 1148 | let mut iter = Attributes::new(r#"tag key "# , 3);
|
| 1149 | // 0 ^ = 8
|
| 1150 |
|
| 1151 | assert_eq!(iter.next(), Some(Err(AttrError::ExpectedEq(8))));
|
| 1152 | assert_eq!(iter.next(), None);
|
| 1153 | assert_eq!(iter.next(), None);
|
| 1154 | }
|
| 1155 |
|
| 1156 | /// Key is started with an invalid symbol (a single quote in this test).
|
| 1157 | /// Because we do not check validity of keys and values during parsing,
|
| 1158 | /// that invalid attribute will be returned
|
| 1159 | #[test ]
|
| 1160 | fn key_start_invalid() {
|
| 1161 | let mut iter = Attributes::new(r#"tag 'key' = 'value' "# , 3);
|
| 1162 |
|
| 1163 | assert_eq!(
|
| 1164 | iter.next(),
|
| 1165 | Some(Ok(Attribute {
|
| 1166 | key: QName(b"'key'" ),
|
| 1167 | value: Cow::Borrowed(b"value" ),
|
| 1168 | }))
|
| 1169 | );
|
| 1170 | assert_eq!(iter.next(), None);
|
| 1171 | assert_eq!(iter.next(), None);
|
| 1172 | }
|
| 1173 |
|
| 1174 | /// Key contains an invalid symbol (an ampersand in this test).
|
| 1175 | /// Because we do not check validity of keys and values during parsing,
|
| 1176 | /// that invalid attribute will be returned
|
| 1177 | #[test ]
|
| 1178 | fn key_contains_invalid() {
|
| 1179 | let mut iter = Attributes::new(r#"tag key&jey = 'value' "# , 3);
|
| 1180 |
|
| 1181 | assert_eq!(
|
| 1182 | iter.next(),
|
| 1183 | Some(Ok(Attribute {
|
| 1184 | key: QName(b"key&jey" ),
|
| 1185 | value: Cow::Borrowed(b"value" ),
|
| 1186 | }))
|
| 1187 | );
|
| 1188 | assert_eq!(iter.next(), None);
|
| 1189 | assert_eq!(iter.next(), None);
|
| 1190 | }
|
| 1191 |
|
| 1192 | /// Attribute value is missing after `=`
|
| 1193 | #[test ]
|
| 1194 | fn missed_value() {
|
| 1195 | let mut iter = Attributes::new(r#"tag key = "# , 3);
|
| 1196 | // 0 ^ = 10
|
| 1197 |
|
| 1198 | assert_eq!(iter.next(), Some(Err(AttrError::ExpectedValue(10))));
|
| 1199 | assert_eq!(iter.next(), None);
|
| 1200 | assert_eq!(iter.next(), None);
|
| 1201 | }
|
| 1202 | }
|
| 1203 |
|
| 1204 | /// Checks that duplicated attributes correctly reported and recovering is
|
| 1205 | /// possible after that
|
| 1206 | mod duplicated {
|
| 1207 | use super::*;
|
| 1208 |
|
| 1209 | mod with_check {
|
| 1210 | use super::*;
|
| 1211 | use pretty_assertions::assert_eq;
|
| 1212 |
|
| 1213 | /// Attribute have a value enclosed in single quotes
|
| 1214 | #[test ]
|
| 1215 | fn single_quoted() {
|
| 1216 | let mut iter = Attributes::new(r#"tag key='value' key='dup' another=''"# , 3);
|
| 1217 | // 0 ^ = 4 ^ = 16
|
| 1218 |
|
| 1219 | assert_eq!(
|
| 1220 | iter.next(),
|
| 1221 | Some(Ok(Attribute {
|
| 1222 | key: QName(b"key" ),
|
| 1223 | value: Cow::Borrowed(b"value" ),
|
| 1224 | }))
|
| 1225 | );
|
| 1226 | assert_eq!(iter.next(), Some(Err(AttrError::Duplicated(16, 4))));
|
| 1227 | assert_eq!(
|
| 1228 | iter.next(),
|
| 1229 | Some(Ok(Attribute {
|
| 1230 | key: QName(b"another" ),
|
| 1231 | value: Cow::Borrowed(b"" ),
|
| 1232 | }))
|
| 1233 | );
|
| 1234 | assert_eq!(iter.next(), None);
|
| 1235 | assert_eq!(iter.next(), None);
|
| 1236 | }
|
| 1237 |
|
| 1238 | /// Attribute have a value enclosed in double quotes
|
| 1239 | #[test ]
|
| 1240 | fn double_quoted() {
|
| 1241 | let mut iter = Attributes::new(r#"tag key='value' key="dup" another=''"# , 3);
|
| 1242 | // 0 ^ = 4 ^ = 16
|
| 1243 |
|
| 1244 | assert_eq!(
|
| 1245 | iter.next(),
|
| 1246 | Some(Ok(Attribute {
|
| 1247 | key: QName(b"key" ),
|
| 1248 | value: Cow::Borrowed(b"value" ),
|
| 1249 | }))
|
| 1250 | );
|
| 1251 | assert_eq!(iter.next(), Some(Err(AttrError::Duplicated(16, 4))));
|
| 1252 | assert_eq!(
|
| 1253 | iter.next(),
|
| 1254 | Some(Ok(Attribute {
|
| 1255 | key: QName(b"another" ),
|
| 1256 | value: Cow::Borrowed(b"" ),
|
| 1257 | }))
|
| 1258 | );
|
| 1259 | assert_eq!(iter.next(), None);
|
| 1260 | assert_eq!(iter.next(), None);
|
| 1261 | }
|
| 1262 |
|
| 1263 | /// Attribute have a value, not enclosed in quotes
|
| 1264 | #[test ]
|
| 1265 | fn unquoted() {
|
| 1266 | let mut iter = Attributes::new(r#"tag key='value' key=dup another=''"# , 3);
|
| 1267 | // 0 ^ = 4 ^ = 16
|
| 1268 |
|
| 1269 | assert_eq!(
|
| 1270 | iter.next(),
|
| 1271 | Some(Ok(Attribute {
|
| 1272 | key: QName(b"key" ),
|
| 1273 | value: Cow::Borrowed(b"value" ),
|
| 1274 | }))
|
| 1275 | );
|
| 1276 | assert_eq!(iter.next(), Some(Err(AttrError::Duplicated(16, 4))));
|
| 1277 | assert_eq!(
|
| 1278 | iter.next(),
|
| 1279 | Some(Ok(Attribute {
|
| 1280 | key: QName(b"another" ),
|
| 1281 | value: Cow::Borrowed(b"" ),
|
| 1282 | }))
|
| 1283 | );
|
| 1284 | assert_eq!(iter.next(), None);
|
| 1285 | assert_eq!(iter.next(), None);
|
| 1286 | }
|
| 1287 |
|
| 1288 | /// Only attribute key is present
|
| 1289 | #[test ]
|
| 1290 | fn key_only() {
|
| 1291 | let mut iter = Attributes::new(r#"tag key='value' key another=''"# , 3);
|
| 1292 | // 0 ^ = 20
|
| 1293 |
|
| 1294 | assert_eq!(
|
| 1295 | iter.next(),
|
| 1296 | Some(Ok(Attribute {
|
| 1297 | key: QName(b"key" ),
|
| 1298 | value: Cow::Borrowed(b"value" ),
|
| 1299 | }))
|
| 1300 | );
|
| 1301 | assert_eq!(iter.next(), Some(Err(AttrError::ExpectedEq(20))));
|
| 1302 | assert_eq!(
|
| 1303 | iter.next(),
|
| 1304 | Some(Ok(Attribute {
|
| 1305 | key: QName(b"another" ),
|
| 1306 | value: Cow::Borrowed(b"" ),
|
| 1307 | }))
|
| 1308 | );
|
| 1309 | assert_eq!(iter.next(), None);
|
| 1310 | assert_eq!(iter.next(), None);
|
| 1311 | }
|
| 1312 | }
|
| 1313 |
|
| 1314 | /// Check for duplicated names is disabled
|
| 1315 | mod without_check {
|
| 1316 | use super::*;
|
| 1317 | use pretty_assertions::assert_eq;
|
| 1318 |
|
| 1319 | /// Attribute have a value enclosed in single quotes
|
| 1320 | #[test ]
|
| 1321 | fn single_quoted() {
|
| 1322 | let mut iter = Attributes::new(r#"tag key='value' key='dup' another=''"# , 3);
|
| 1323 | iter.with_checks(false);
|
| 1324 |
|
| 1325 | assert_eq!(
|
| 1326 | iter.next(),
|
| 1327 | Some(Ok(Attribute {
|
| 1328 | key: QName(b"key" ),
|
| 1329 | value: Cow::Borrowed(b"value" ),
|
| 1330 | }))
|
| 1331 | );
|
| 1332 | assert_eq!(
|
| 1333 | iter.next(),
|
| 1334 | Some(Ok(Attribute {
|
| 1335 | key: QName(b"key" ),
|
| 1336 | value: Cow::Borrowed(b"dup" ),
|
| 1337 | }))
|
| 1338 | );
|
| 1339 | assert_eq!(
|
| 1340 | iter.next(),
|
| 1341 | Some(Ok(Attribute {
|
| 1342 | key: QName(b"another" ),
|
| 1343 | value: Cow::Borrowed(b"" ),
|
| 1344 | }))
|
| 1345 | );
|
| 1346 | assert_eq!(iter.next(), None);
|
| 1347 | assert_eq!(iter.next(), None);
|
| 1348 | }
|
| 1349 |
|
| 1350 | /// Attribute have a value enclosed in double quotes
|
| 1351 | #[test ]
|
| 1352 | fn double_quoted() {
|
| 1353 | let mut iter = Attributes::new(r#"tag key='value' key="dup" another=''"# , 3);
|
| 1354 | iter.with_checks(false);
|
| 1355 |
|
| 1356 | assert_eq!(
|
| 1357 | iter.next(),
|
| 1358 | Some(Ok(Attribute {
|
| 1359 | key: QName(b"key" ),
|
| 1360 | value: Cow::Borrowed(b"value" ),
|
| 1361 | }))
|
| 1362 | );
|
| 1363 | assert_eq!(
|
| 1364 | iter.next(),
|
| 1365 | Some(Ok(Attribute {
|
| 1366 | key: QName(b"key" ),
|
| 1367 | value: Cow::Borrowed(b"dup" ),
|
| 1368 | }))
|
| 1369 | );
|
| 1370 | assert_eq!(
|
| 1371 | iter.next(),
|
| 1372 | Some(Ok(Attribute {
|
| 1373 | key: QName(b"another" ),
|
| 1374 | value: Cow::Borrowed(b"" ),
|
| 1375 | }))
|
| 1376 | );
|
| 1377 | assert_eq!(iter.next(), None);
|
| 1378 | assert_eq!(iter.next(), None);
|
| 1379 | }
|
| 1380 |
|
| 1381 | /// Attribute have a value, not enclosed in quotes
|
| 1382 | #[test ]
|
| 1383 | fn unquoted() {
|
| 1384 | let mut iter = Attributes::new(r#"tag key='value' key=dup another=''"# , 3);
|
| 1385 | // 0 ^ = 20
|
| 1386 | iter.with_checks(false);
|
| 1387 |
|
| 1388 | assert_eq!(
|
| 1389 | iter.next(),
|
| 1390 | Some(Ok(Attribute {
|
| 1391 | key: QName(b"key" ),
|
| 1392 | value: Cow::Borrowed(b"value" ),
|
| 1393 | }))
|
| 1394 | );
|
| 1395 | assert_eq!(iter.next(), Some(Err(AttrError::UnquotedValue(20))));
|
| 1396 | assert_eq!(
|
| 1397 | iter.next(),
|
| 1398 | Some(Ok(Attribute {
|
| 1399 | key: QName(b"another" ),
|
| 1400 | value: Cow::Borrowed(b"" ),
|
| 1401 | }))
|
| 1402 | );
|
| 1403 | assert_eq!(iter.next(), None);
|
| 1404 | assert_eq!(iter.next(), None);
|
| 1405 | }
|
| 1406 |
|
| 1407 | /// Only attribute key is present
|
| 1408 | #[test ]
|
| 1409 | fn key_only() {
|
| 1410 | let mut iter = Attributes::new(r#"tag key='value' key another=''"# , 3);
|
| 1411 | // 0 ^ = 20
|
| 1412 | iter.with_checks(false);
|
| 1413 |
|
| 1414 | assert_eq!(
|
| 1415 | iter.next(),
|
| 1416 | Some(Ok(Attribute {
|
| 1417 | key: QName(b"key" ),
|
| 1418 | value: Cow::Borrowed(b"value" ),
|
| 1419 | }))
|
| 1420 | );
|
| 1421 | assert_eq!(iter.next(), Some(Err(AttrError::ExpectedEq(20))));
|
| 1422 | assert_eq!(
|
| 1423 | iter.next(),
|
| 1424 | Some(Ok(Attribute {
|
| 1425 | key: QName(b"another" ),
|
| 1426 | value: Cow::Borrowed(b"" ),
|
| 1427 | }))
|
| 1428 | );
|
| 1429 | assert_eq!(iter.next(), None);
|
| 1430 | assert_eq!(iter.next(), None);
|
| 1431 | }
|
| 1432 | }
|
| 1433 | }
|
| 1434 |
|
| 1435 | #[test ]
|
| 1436 | fn mixed_quote() {
|
| 1437 | let mut iter = Attributes::new(r#"tag a='a' b = "b" c='cc"cc' d="dd'dd""# , 3);
|
| 1438 |
|
| 1439 | assert_eq!(
|
| 1440 | iter.next(),
|
| 1441 | Some(Ok(Attribute {
|
| 1442 | key: QName(b"a" ),
|
| 1443 | value: Cow::Borrowed(b"a" ),
|
| 1444 | }))
|
| 1445 | );
|
| 1446 | assert_eq!(
|
| 1447 | iter.next(),
|
| 1448 | Some(Ok(Attribute {
|
| 1449 | key: QName(b"b" ),
|
| 1450 | value: Cow::Borrowed(b"b" ),
|
| 1451 | }))
|
| 1452 | );
|
| 1453 | assert_eq!(
|
| 1454 | iter.next(),
|
| 1455 | Some(Ok(Attribute {
|
| 1456 | key: QName(b"c" ),
|
| 1457 | value: Cow::Borrowed(br#"cc"cc"# ),
|
| 1458 | }))
|
| 1459 | );
|
| 1460 | assert_eq!(
|
| 1461 | iter.next(),
|
| 1462 | Some(Ok(Attribute {
|
| 1463 | key: QName(b"d" ),
|
| 1464 | value: Cow::Borrowed(b"dd'dd" ),
|
| 1465 | }))
|
| 1466 | );
|
| 1467 | assert_eq!(iter.next(), None);
|
| 1468 | assert_eq!(iter.next(), None);
|
| 1469 | }
|
| 1470 | }
|
| 1471 |
|
| 1472 | /// Checks, how parsing of HTML-style attributes works. Each attribute can be
|
| 1473 | /// in three forms:
|
| 1474 | /// - XML-like: have a value, enclosed in single or double quotes
|
| 1475 | /// - have a value, do not enclosed in quotes
|
| 1476 | /// - without value, key only
|
| 1477 | #[cfg (test)]
|
| 1478 | mod html {
|
| 1479 | use super::*;
|
| 1480 | use pretty_assertions::assert_eq;
|
| 1481 |
|
| 1482 | /// Checked attribute is the single attribute
|
| 1483 | mod single {
|
| 1484 | use super::*;
|
| 1485 | use pretty_assertions::assert_eq;
|
| 1486 |
|
| 1487 | /// Attribute have a value enclosed in single quotes
|
| 1488 | #[test ]
|
| 1489 | fn single_quoted() {
|
| 1490 | let mut iter = Attributes::html(r#"tag key='value'"# , 3);
|
| 1491 |
|
| 1492 | assert_eq!(
|
| 1493 | iter.next(),
|
| 1494 | Some(Ok(Attribute {
|
| 1495 | key: QName(b"key" ),
|
| 1496 | value: Cow::Borrowed(b"value" ),
|
| 1497 | }))
|
| 1498 | );
|
| 1499 | assert_eq!(iter.next(), None);
|
| 1500 | assert_eq!(iter.next(), None);
|
| 1501 | }
|
| 1502 |
|
| 1503 | /// Attribute have a value enclosed in double quotes
|
| 1504 | #[test ]
|
| 1505 | fn double_quoted() {
|
| 1506 | let mut iter = Attributes::html(r#"tag key="value""# , 3);
|
| 1507 |
|
| 1508 | assert_eq!(
|
| 1509 | iter.next(),
|
| 1510 | Some(Ok(Attribute {
|
| 1511 | key: QName(b"key" ),
|
| 1512 | value: Cow::Borrowed(b"value" ),
|
| 1513 | }))
|
| 1514 | );
|
| 1515 | assert_eq!(iter.next(), None);
|
| 1516 | assert_eq!(iter.next(), None);
|
| 1517 | }
|
| 1518 |
|
| 1519 | /// Attribute have a value, not enclosed in quotes
|
| 1520 | #[test ]
|
| 1521 | fn unquoted() {
|
| 1522 | let mut iter = Attributes::html(r#"tag key=value"# , 3);
|
| 1523 |
|
| 1524 | assert_eq!(
|
| 1525 | iter.next(),
|
| 1526 | Some(Ok(Attribute {
|
| 1527 | key: QName(b"key" ),
|
| 1528 | value: Cow::Borrowed(b"value" ),
|
| 1529 | }))
|
| 1530 | );
|
| 1531 | assert_eq!(iter.next(), None);
|
| 1532 | assert_eq!(iter.next(), None);
|
| 1533 | }
|
| 1534 |
|
| 1535 | /// Only attribute key is present
|
| 1536 | #[test ]
|
| 1537 | fn key_only() {
|
| 1538 | let mut iter = Attributes::html(r#"tag key"# , 3);
|
| 1539 |
|
| 1540 | assert_eq!(
|
| 1541 | iter.next(),
|
| 1542 | Some(Ok(Attribute {
|
| 1543 | key: QName(b"key" ),
|
| 1544 | value: Cow::Borrowed(&[]),
|
| 1545 | }))
|
| 1546 | );
|
| 1547 | assert_eq!(iter.next(), None);
|
| 1548 | assert_eq!(iter.next(), None);
|
| 1549 | }
|
| 1550 |
|
| 1551 | /// Key is started with an invalid symbol (a single quote in this test).
|
| 1552 | /// Because we do not check validity of keys and values during parsing,
|
| 1553 | /// that invalid attribute will be returned
|
| 1554 | #[test ]
|
| 1555 | fn key_start_invalid() {
|
| 1556 | let mut iter = Attributes::html(r#"tag 'key'='value'"# , 3);
|
| 1557 |
|
| 1558 | assert_eq!(
|
| 1559 | iter.next(),
|
| 1560 | Some(Ok(Attribute {
|
| 1561 | key: QName(b"'key'" ),
|
| 1562 | value: Cow::Borrowed(b"value" ),
|
| 1563 | }))
|
| 1564 | );
|
| 1565 | assert_eq!(iter.next(), None);
|
| 1566 | assert_eq!(iter.next(), None);
|
| 1567 | }
|
| 1568 |
|
| 1569 | /// Key contains an invalid symbol (an ampersand in this test).
|
| 1570 | /// Because we do not check validity of keys and values during parsing,
|
| 1571 | /// that invalid attribute will be returned
|
| 1572 | #[test ]
|
| 1573 | fn key_contains_invalid() {
|
| 1574 | let mut iter = Attributes::html(r#"tag key&jey='value'"# , 3);
|
| 1575 |
|
| 1576 | assert_eq!(
|
| 1577 | iter.next(),
|
| 1578 | Some(Ok(Attribute {
|
| 1579 | key: QName(b"key&jey" ),
|
| 1580 | value: Cow::Borrowed(b"value" ),
|
| 1581 | }))
|
| 1582 | );
|
| 1583 | assert_eq!(iter.next(), None);
|
| 1584 | assert_eq!(iter.next(), None);
|
| 1585 | }
|
| 1586 |
|
| 1587 | /// Attribute value is missing after `=`
|
| 1588 | #[test ]
|
| 1589 | fn missed_value() {
|
| 1590 | let mut iter = Attributes::html(r#"tag key="# , 3);
|
| 1591 | // 0 ^ = 8
|
| 1592 |
|
| 1593 | assert_eq!(iter.next(), Some(Err(AttrError::ExpectedValue(8))));
|
| 1594 | assert_eq!(iter.next(), None);
|
| 1595 | assert_eq!(iter.next(), None);
|
| 1596 | }
|
| 1597 | }
|
| 1598 |
|
| 1599 | /// Checked attribute is the first attribute in the list of many attributes
|
| 1600 | mod first {
|
| 1601 | use super::*;
|
| 1602 | use pretty_assertions::assert_eq;
|
| 1603 |
|
| 1604 | /// Attribute have a value enclosed in single quotes
|
| 1605 | #[test ]
|
| 1606 | fn single_quoted() {
|
| 1607 | let mut iter = Attributes::html(r#"tag key='value' regular='attribute'"# , 3);
|
| 1608 |
|
| 1609 | assert_eq!(
|
| 1610 | iter.next(),
|
| 1611 | Some(Ok(Attribute {
|
| 1612 | key: QName(b"key" ),
|
| 1613 | value: Cow::Borrowed(b"value" ),
|
| 1614 | }))
|
| 1615 | );
|
| 1616 | assert_eq!(
|
| 1617 | iter.next(),
|
| 1618 | Some(Ok(Attribute {
|
| 1619 | key: QName(b"regular" ),
|
| 1620 | value: Cow::Borrowed(b"attribute" ),
|
| 1621 | }))
|
| 1622 | );
|
| 1623 | assert_eq!(iter.next(), None);
|
| 1624 | assert_eq!(iter.next(), None);
|
| 1625 | }
|
| 1626 |
|
| 1627 | /// Attribute have a value enclosed in double quotes
|
| 1628 | #[test ]
|
| 1629 | fn double_quoted() {
|
| 1630 | let mut iter = Attributes::html(r#"tag key="value" regular='attribute'"# , 3);
|
| 1631 |
|
| 1632 | assert_eq!(
|
| 1633 | iter.next(),
|
| 1634 | Some(Ok(Attribute {
|
| 1635 | key: QName(b"key" ),
|
| 1636 | value: Cow::Borrowed(b"value" ),
|
| 1637 | }))
|
| 1638 | );
|
| 1639 | assert_eq!(
|
| 1640 | iter.next(),
|
| 1641 | Some(Ok(Attribute {
|
| 1642 | key: QName(b"regular" ),
|
| 1643 | value: Cow::Borrowed(b"attribute" ),
|
| 1644 | }))
|
| 1645 | );
|
| 1646 | assert_eq!(iter.next(), None);
|
| 1647 | assert_eq!(iter.next(), None);
|
| 1648 | }
|
| 1649 |
|
| 1650 | /// Attribute have a value, not enclosed in quotes
|
| 1651 | #[test ]
|
| 1652 | fn unquoted() {
|
| 1653 | let mut iter = Attributes::html(r#"tag key=value regular='attribute'"# , 3);
|
| 1654 |
|
| 1655 | assert_eq!(
|
| 1656 | iter.next(),
|
| 1657 | Some(Ok(Attribute {
|
| 1658 | key: QName(b"key" ),
|
| 1659 | value: Cow::Borrowed(b"value" ),
|
| 1660 | }))
|
| 1661 | );
|
| 1662 | assert_eq!(
|
| 1663 | iter.next(),
|
| 1664 | Some(Ok(Attribute {
|
| 1665 | key: QName(b"regular" ),
|
| 1666 | value: Cow::Borrowed(b"attribute" ),
|
| 1667 | }))
|
| 1668 | );
|
| 1669 | assert_eq!(iter.next(), None);
|
| 1670 | assert_eq!(iter.next(), None);
|
| 1671 | }
|
| 1672 |
|
| 1673 | /// Only attribute key is present
|
| 1674 | #[test ]
|
| 1675 | fn key_only() {
|
| 1676 | let mut iter = Attributes::html(r#"tag key regular='attribute'"# , 3);
|
| 1677 |
|
| 1678 | assert_eq!(
|
| 1679 | iter.next(),
|
| 1680 | Some(Ok(Attribute {
|
| 1681 | key: QName(b"key" ),
|
| 1682 | value: Cow::Borrowed(&[]),
|
| 1683 | }))
|
| 1684 | );
|
| 1685 | assert_eq!(
|
| 1686 | iter.next(),
|
| 1687 | Some(Ok(Attribute {
|
| 1688 | key: QName(b"regular" ),
|
| 1689 | value: Cow::Borrowed(b"attribute" ),
|
| 1690 | }))
|
| 1691 | );
|
| 1692 | assert_eq!(iter.next(), None);
|
| 1693 | assert_eq!(iter.next(), None);
|
| 1694 | }
|
| 1695 |
|
| 1696 | /// Key is started with an invalid symbol (a single quote in this test).
|
| 1697 | /// Because we do not check validity of keys and values during parsing,
|
| 1698 | /// that invalid attribute will be returned
|
| 1699 | #[test ]
|
| 1700 | fn key_start_invalid() {
|
| 1701 | let mut iter = Attributes::html(r#"tag 'key'='value' regular='attribute'"# , 3);
|
| 1702 |
|
| 1703 | assert_eq!(
|
| 1704 | iter.next(),
|
| 1705 | Some(Ok(Attribute {
|
| 1706 | key: QName(b"'key'" ),
|
| 1707 | value: Cow::Borrowed(b"value" ),
|
| 1708 | }))
|
| 1709 | );
|
| 1710 | assert_eq!(
|
| 1711 | iter.next(),
|
| 1712 | Some(Ok(Attribute {
|
| 1713 | key: QName(b"regular" ),
|
| 1714 | value: Cow::Borrowed(b"attribute" ),
|
| 1715 | }))
|
| 1716 | );
|
| 1717 | assert_eq!(iter.next(), None);
|
| 1718 | assert_eq!(iter.next(), None);
|
| 1719 | }
|
| 1720 |
|
| 1721 | /// Key contains an invalid symbol (an ampersand in this test).
|
| 1722 | /// Because we do not check validity of keys and values during parsing,
|
| 1723 | /// that invalid attribute will be returned
|
| 1724 | #[test ]
|
| 1725 | fn key_contains_invalid() {
|
| 1726 | let mut iter = Attributes::html(r#"tag key&jey='value' regular='attribute'"# , 3);
|
| 1727 |
|
| 1728 | assert_eq!(
|
| 1729 | iter.next(),
|
| 1730 | Some(Ok(Attribute {
|
| 1731 | key: QName(b"key&jey" ),
|
| 1732 | value: Cow::Borrowed(b"value" ),
|
| 1733 | }))
|
| 1734 | );
|
| 1735 | assert_eq!(
|
| 1736 | iter.next(),
|
| 1737 | Some(Ok(Attribute {
|
| 1738 | key: QName(b"regular" ),
|
| 1739 | value: Cow::Borrowed(b"attribute" ),
|
| 1740 | }))
|
| 1741 | );
|
| 1742 | assert_eq!(iter.next(), None);
|
| 1743 | assert_eq!(iter.next(), None);
|
| 1744 | }
|
| 1745 |
|
| 1746 | /// Attribute value is missing after `=`
|
| 1747 | #[test ]
|
| 1748 | fn missed_value() {
|
| 1749 | let mut iter = Attributes::html(r#"tag key= regular='attribute'"# , 3);
|
| 1750 |
|
| 1751 | // Because we do not check validity of keys and values during parsing,
|
| 1752 | // "regular='attribute'" is considered as unquoted attribute value
|
| 1753 | assert_eq!(
|
| 1754 | iter.next(),
|
| 1755 | Some(Ok(Attribute {
|
| 1756 | key: QName(b"key" ),
|
| 1757 | value: Cow::Borrowed(b"regular='attribute'" ),
|
| 1758 | }))
|
| 1759 | );
|
| 1760 | assert_eq!(iter.next(), None);
|
| 1761 | assert_eq!(iter.next(), None);
|
| 1762 |
|
| 1763 | ////////////////////////////////////////////////////////////////////
|
| 1764 |
|
| 1765 | let mut iter = Attributes::html(r#"tag key= regular= 'attribute'"# , 3);
|
| 1766 |
|
| 1767 | // Because we do not check validity of keys and values during parsing,
|
| 1768 | // "regular=" is considered as unquoted attribute value
|
| 1769 | assert_eq!(
|
| 1770 | iter.next(),
|
| 1771 | Some(Ok(Attribute {
|
| 1772 | key: QName(b"key" ),
|
| 1773 | value: Cow::Borrowed(b"regular=" ),
|
| 1774 | }))
|
| 1775 | );
|
| 1776 | // Because we do not check validity of keys and values during parsing,
|
| 1777 | // "'attribute'" is considered as key-only attribute
|
| 1778 | assert_eq!(
|
| 1779 | iter.next(),
|
| 1780 | Some(Ok(Attribute {
|
| 1781 | key: QName(b"'attribute'" ),
|
| 1782 | value: Cow::Borrowed(&[]),
|
| 1783 | }))
|
| 1784 | );
|
| 1785 | assert_eq!(iter.next(), None);
|
| 1786 | assert_eq!(iter.next(), None);
|
| 1787 |
|
| 1788 | ////////////////////////////////////////////////////////////////////
|
| 1789 |
|
| 1790 | let mut iter = Attributes::html(r#"tag key= regular ='attribute'"# , 3);
|
| 1791 |
|
| 1792 | // Because we do not check validity of keys and values during parsing,
|
| 1793 | // "regular" is considered as unquoted attribute value
|
| 1794 | assert_eq!(
|
| 1795 | iter.next(),
|
| 1796 | Some(Ok(Attribute {
|
| 1797 | key: QName(b"key" ),
|
| 1798 | value: Cow::Borrowed(b"regular" ),
|
| 1799 | }))
|
| 1800 | );
|
| 1801 | // Because we do not check validity of keys and values during parsing,
|
| 1802 | // "='attribute'" is considered as key-only attribute
|
| 1803 | assert_eq!(
|
| 1804 | iter.next(),
|
| 1805 | Some(Ok(Attribute {
|
| 1806 | key: QName(b"='attribute'" ),
|
| 1807 | value: Cow::Borrowed(&[]),
|
| 1808 | }))
|
| 1809 | );
|
| 1810 | assert_eq!(iter.next(), None);
|
| 1811 | assert_eq!(iter.next(), None);
|
| 1812 |
|
| 1813 | ////////////////////////////////////////////////////////////////////
|
| 1814 |
|
| 1815 | let mut iter = Attributes::html(r#"tag key= regular = 'attribute'"# , 3);
|
| 1816 | // 0 ^ = 9 ^ = 19 ^ = 30
|
| 1817 |
|
| 1818 | // Because we do not check validity of keys and values during parsing,
|
| 1819 | // "regular" is considered as unquoted attribute value
|
| 1820 | assert_eq!(
|
| 1821 | iter.next(),
|
| 1822 | Some(Ok(Attribute {
|
| 1823 | key: QName(b"key" ),
|
| 1824 | value: Cow::Borrowed(b"regular" ),
|
| 1825 | }))
|
| 1826 | );
|
| 1827 | // Because we do not check validity of keys and values during parsing,
|
| 1828 | // "=" is considered as key-only attribute
|
| 1829 | assert_eq!(
|
| 1830 | iter.next(),
|
| 1831 | Some(Ok(Attribute {
|
| 1832 | key: QName(b"=" ),
|
| 1833 | value: Cow::Borrowed(&[]),
|
| 1834 | }))
|
| 1835 | );
|
| 1836 | // Because we do not check validity of keys and values during parsing,
|
| 1837 | // "'attribute'" is considered as key-only attribute
|
| 1838 | assert_eq!(
|
| 1839 | iter.next(),
|
| 1840 | Some(Ok(Attribute {
|
| 1841 | key: QName(b"'attribute'" ),
|
| 1842 | value: Cow::Borrowed(&[]),
|
| 1843 | }))
|
| 1844 | );
|
| 1845 | assert_eq!(iter.next(), None);
|
| 1846 | assert_eq!(iter.next(), None);
|
| 1847 | }
|
| 1848 | }
|
| 1849 |
|
| 1850 | /// Copy of single, but with additional spaces in markup
|
| 1851 | mod sparsed {
|
| 1852 | use super::*;
|
| 1853 | use pretty_assertions::assert_eq;
|
| 1854 |
|
| 1855 | /// Attribute have a value enclosed in single quotes
|
| 1856 | #[test ]
|
| 1857 | fn single_quoted() {
|
| 1858 | let mut iter = Attributes::html(r#"tag key = 'value' "# , 3);
|
| 1859 |
|
| 1860 | assert_eq!(
|
| 1861 | iter.next(),
|
| 1862 | Some(Ok(Attribute {
|
| 1863 | key: QName(b"key" ),
|
| 1864 | value: Cow::Borrowed(b"value" ),
|
| 1865 | }))
|
| 1866 | );
|
| 1867 | assert_eq!(iter.next(), None);
|
| 1868 | assert_eq!(iter.next(), None);
|
| 1869 | }
|
| 1870 |
|
| 1871 | /// Attribute have a value enclosed in double quotes
|
| 1872 | #[test ]
|
| 1873 | fn double_quoted() {
|
| 1874 | let mut iter = Attributes::html(r#"tag key = "value" "# , 3);
|
| 1875 |
|
| 1876 | assert_eq!(
|
| 1877 | iter.next(),
|
| 1878 | Some(Ok(Attribute {
|
| 1879 | key: QName(b"key" ),
|
| 1880 | value: Cow::Borrowed(b"value" ),
|
| 1881 | }))
|
| 1882 | );
|
| 1883 | assert_eq!(iter.next(), None);
|
| 1884 | assert_eq!(iter.next(), None);
|
| 1885 | }
|
| 1886 |
|
| 1887 | /// Attribute have a value, not enclosed in quotes
|
| 1888 | #[test ]
|
| 1889 | fn unquoted() {
|
| 1890 | let mut iter = Attributes::html(r#"tag key = value "# , 3);
|
| 1891 |
|
| 1892 | assert_eq!(
|
| 1893 | iter.next(),
|
| 1894 | Some(Ok(Attribute {
|
| 1895 | key: QName(b"key" ),
|
| 1896 | value: Cow::Borrowed(b"value" ),
|
| 1897 | }))
|
| 1898 | );
|
| 1899 | assert_eq!(iter.next(), None);
|
| 1900 | assert_eq!(iter.next(), None);
|
| 1901 | }
|
| 1902 |
|
| 1903 | /// Only attribute key is present
|
| 1904 | #[test ]
|
| 1905 | fn key_only() {
|
| 1906 | let mut iter = Attributes::html(r#"tag key "# , 3);
|
| 1907 |
|
| 1908 | assert_eq!(
|
| 1909 | iter.next(),
|
| 1910 | Some(Ok(Attribute {
|
| 1911 | key: QName(b"key" ),
|
| 1912 | value: Cow::Borrowed(&[]),
|
| 1913 | }))
|
| 1914 | );
|
| 1915 | assert_eq!(iter.next(), None);
|
| 1916 | assert_eq!(iter.next(), None);
|
| 1917 | }
|
| 1918 |
|
| 1919 | /// Key is started with an invalid symbol (a single quote in this test).
|
| 1920 | /// Because we do not check validity of keys and values during parsing,
|
| 1921 | /// that invalid attribute will be returned
|
| 1922 | #[test ]
|
| 1923 | fn key_start_invalid() {
|
| 1924 | let mut iter = Attributes::html(r#"tag 'key' = 'value' "# , 3);
|
| 1925 |
|
| 1926 | assert_eq!(
|
| 1927 | iter.next(),
|
| 1928 | Some(Ok(Attribute {
|
| 1929 | key: QName(b"'key'" ),
|
| 1930 | value: Cow::Borrowed(b"value" ),
|
| 1931 | }))
|
| 1932 | );
|
| 1933 | assert_eq!(iter.next(), None);
|
| 1934 | assert_eq!(iter.next(), None);
|
| 1935 | }
|
| 1936 |
|
| 1937 | /// Key contains an invalid symbol (an ampersand in this test).
|
| 1938 | /// Because we do not check validity of keys and values during parsing,
|
| 1939 | /// that invalid attribute will be returned
|
| 1940 | #[test ]
|
| 1941 | fn key_contains_invalid() {
|
| 1942 | let mut iter = Attributes::html(r#"tag key&jey = 'value' "# , 3);
|
| 1943 |
|
| 1944 | assert_eq!(
|
| 1945 | iter.next(),
|
| 1946 | Some(Ok(Attribute {
|
| 1947 | key: QName(b"key&jey" ),
|
| 1948 | value: Cow::Borrowed(b"value" ),
|
| 1949 | }))
|
| 1950 | );
|
| 1951 | assert_eq!(iter.next(), None);
|
| 1952 | assert_eq!(iter.next(), None);
|
| 1953 | }
|
| 1954 |
|
| 1955 | /// Attribute value is missing after `=`
|
| 1956 | #[test ]
|
| 1957 | fn missed_value() {
|
| 1958 | let mut iter = Attributes::html(r#"tag key = "# , 3);
|
| 1959 | // 0 ^ = 10
|
| 1960 |
|
| 1961 | assert_eq!(iter.next(), Some(Err(AttrError::ExpectedValue(10))));
|
| 1962 | assert_eq!(iter.next(), None);
|
| 1963 | assert_eq!(iter.next(), None);
|
| 1964 | }
|
| 1965 | }
|
| 1966 |
|
| 1967 | /// Checks that duplicated attributes correctly reported and recovering is
|
| 1968 | /// possible after that
|
| 1969 | mod duplicated {
|
| 1970 | use super::*;
|
| 1971 |
|
| 1972 | mod with_check {
|
| 1973 | use super::*;
|
| 1974 | use pretty_assertions::assert_eq;
|
| 1975 |
|
| 1976 | /// Attribute have a value enclosed in single quotes
|
| 1977 | #[test ]
|
| 1978 | fn single_quoted() {
|
| 1979 | let mut iter = Attributes::html(r#"tag key='value' key='dup' another=''"# , 3);
|
| 1980 | // 0 ^ = 4 ^ = 16
|
| 1981 |
|
| 1982 | assert_eq!(
|
| 1983 | iter.next(),
|
| 1984 | Some(Ok(Attribute {
|
| 1985 | key: QName(b"key" ),
|
| 1986 | value: Cow::Borrowed(b"value" ),
|
| 1987 | }))
|
| 1988 | );
|
| 1989 | assert_eq!(iter.next(), Some(Err(AttrError::Duplicated(16, 4))));
|
| 1990 | assert_eq!(
|
| 1991 | iter.next(),
|
| 1992 | Some(Ok(Attribute {
|
| 1993 | key: QName(b"another" ),
|
| 1994 | value: Cow::Borrowed(b"" ),
|
| 1995 | }))
|
| 1996 | );
|
| 1997 | assert_eq!(iter.next(), None);
|
| 1998 | assert_eq!(iter.next(), None);
|
| 1999 | }
|
| 2000 |
|
| 2001 | /// Attribute have a value enclosed in double quotes
|
| 2002 | #[test ]
|
| 2003 | fn double_quoted() {
|
| 2004 | let mut iter = Attributes::html(r#"tag key='value' key="dup" another=''"# , 3);
|
| 2005 | // 0 ^ = 4 ^ = 16
|
| 2006 |
|
| 2007 | assert_eq!(
|
| 2008 | iter.next(),
|
| 2009 | Some(Ok(Attribute {
|
| 2010 | key: QName(b"key" ),
|
| 2011 | value: Cow::Borrowed(b"value" ),
|
| 2012 | }))
|
| 2013 | );
|
| 2014 | assert_eq!(iter.next(), Some(Err(AttrError::Duplicated(16, 4))));
|
| 2015 | assert_eq!(
|
| 2016 | iter.next(),
|
| 2017 | Some(Ok(Attribute {
|
| 2018 | key: QName(b"another" ),
|
| 2019 | value: Cow::Borrowed(b"" ),
|
| 2020 | }))
|
| 2021 | );
|
| 2022 | assert_eq!(iter.next(), None);
|
| 2023 | assert_eq!(iter.next(), None);
|
| 2024 | }
|
| 2025 |
|
| 2026 | /// Attribute have a value, not enclosed in quotes
|
| 2027 | #[test ]
|
| 2028 | fn unquoted() {
|
| 2029 | let mut iter = Attributes::html(r#"tag key='value' key=dup another=''"# , 3);
|
| 2030 | // 0 ^ = 4 ^ = 16
|
| 2031 |
|
| 2032 | assert_eq!(
|
| 2033 | iter.next(),
|
| 2034 | Some(Ok(Attribute {
|
| 2035 | key: QName(b"key" ),
|
| 2036 | value: Cow::Borrowed(b"value" ),
|
| 2037 | }))
|
| 2038 | );
|
| 2039 | assert_eq!(iter.next(), Some(Err(AttrError::Duplicated(16, 4))));
|
| 2040 | assert_eq!(
|
| 2041 | iter.next(),
|
| 2042 | Some(Ok(Attribute {
|
| 2043 | key: QName(b"another" ),
|
| 2044 | value: Cow::Borrowed(b"" ),
|
| 2045 | }))
|
| 2046 | );
|
| 2047 | assert_eq!(iter.next(), None);
|
| 2048 | assert_eq!(iter.next(), None);
|
| 2049 | }
|
| 2050 |
|
| 2051 | /// Only attribute key is present
|
| 2052 | #[test ]
|
| 2053 | fn key_only() {
|
| 2054 | let mut iter = Attributes::html(r#"tag key='value' key another=''"# , 3);
|
| 2055 | // 0 ^ = 4 ^ = 16
|
| 2056 |
|
| 2057 | assert_eq!(
|
| 2058 | iter.next(),
|
| 2059 | Some(Ok(Attribute {
|
| 2060 | key: QName(b"key" ),
|
| 2061 | value: Cow::Borrowed(b"value" ),
|
| 2062 | }))
|
| 2063 | );
|
| 2064 | assert_eq!(iter.next(), Some(Err(AttrError::Duplicated(16, 4))));
|
| 2065 | assert_eq!(
|
| 2066 | iter.next(),
|
| 2067 | Some(Ok(Attribute {
|
| 2068 | key: QName(b"another" ),
|
| 2069 | value: Cow::Borrowed(b"" ),
|
| 2070 | }))
|
| 2071 | );
|
| 2072 | assert_eq!(iter.next(), None);
|
| 2073 | assert_eq!(iter.next(), None);
|
| 2074 | }
|
| 2075 | }
|
| 2076 |
|
| 2077 | /// Check for duplicated names is disabled
|
| 2078 | mod without_check {
|
| 2079 | use super::*;
|
| 2080 | use pretty_assertions::assert_eq;
|
| 2081 |
|
| 2082 | /// Attribute have a value enclosed in single quotes
|
| 2083 | #[test ]
|
| 2084 | fn single_quoted() {
|
| 2085 | let mut iter = Attributes::html(r#"tag key='value' key='dup' another=''"# , 3);
|
| 2086 | iter.with_checks(false);
|
| 2087 |
|
| 2088 | assert_eq!(
|
| 2089 | iter.next(),
|
| 2090 | Some(Ok(Attribute {
|
| 2091 | key: QName(b"key" ),
|
| 2092 | value: Cow::Borrowed(b"value" ),
|
| 2093 | }))
|
| 2094 | );
|
| 2095 | assert_eq!(
|
| 2096 | iter.next(),
|
| 2097 | Some(Ok(Attribute {
|
| 2098 | key: QName(b"key" ),
|
| 2099 | value: Cow::Borrowed(b"dup" ),
|
| 2100 | }))
|
| 2101 | );
|
| 2102 | assert_eq!(
|
| 2103 | iter.next(),
|
| 2104 | Some(Ok(Attribute {
|
| 2105 | key: QName(b"another" ),
|
| 2106 | value: Cow::Borrowed(b"" ),
|
| 2107 | }))
|
| 2108 | );
|
| 2109 | assert_eq!(iter.next(), None);
|
| 2110 | assert_eq!(iter.next(), None);
|
| 2111 | }
|
| 2112 |
|
| 2113 | /// Attribute have a value enclosed in double quotes
|
| 2114 | #[test ]
|
| 2115 | fn double_quoted() {
|
| 2116 | let mut iter = Attributes::html(r#"tag key='value' key="dup" another=''"# , 3);
|
| 2117 | iter.with_checks(false);
|
| 2118 |
|
| 2119 | assert_eq!(
|
| 2120 | iter.next(),
|
| 2121 | Some(Ok(Attribute {
|
| 2122 | key: QName(b"key" ),
|
| 2123 | value: Cow::Borrowed(b"value" ),
|
| 2124 | }))
|
| 2125 | );
|
| 2126 | assert_eq!(
|
| 2127 | iter.next(),
|
| 2128 | Some(Ok(Attribute {
|
| 2129 | key: QName(b"key" ),
|
| 2130 | value: Cow::Borrowed(b"dup" ),
|
| 2131 | }))
|
| 2132 | );
|
| 2133 | assert_eq!(
|
| 2134 | iter.next(),
|
| 2135 | Some(Ok(Attribute {
|
| 2136 | key: QName(b"another" ),
|
| 2137 | value: Cow::Borrowed(b"" ),
|
| 2138 | }))
|
| 2139 | );
|
| 2140 | assert_eq!(iter.next(), None);
|
| 2141 | assert_eq!(iter.next(), None);
|
| 2142 | }
|
| 2143 |
|
| 2144 | /// Attribute have a value, not enclosed in quotes
|
| 2145 | #[test ]
|
| 2146 | fn unquoted() {
|
| 2147 | let mut iter = Attributes::html(r#"tag key='value' key=dup another=''"# , 3);
|
| 2148 | iter.with_checks(false);
|
| 2149 |
|
| 2150 | assert_eq!(
|
| 2151 | iter.next(),
|
| 2152 | Some(Ok(Attribute {
|
| 2153 | key: QName(b"key" ),
|
| 2154 | value: Cow::Borrowed(b"value" ),
|
| 2155 | }))
|
| 2156 | );
|
| 2157 | assert_eq!(
|
| 2158 | iter.next(),
|
| 2159 | Some(Ok(Attribute {
|
| 2160 | key: QName(b"key" ),
|
| 2161 | value: Cow::Borrowed(b"dup" ),
|
| 2162 | }))
|
| 2163 | );
|
| 2164 | assert_eq!(
|
| 2165 | iter.next(),
|
| 2166 | Some(Ok(Attribute {
|
| 2167 | key: QName(b"another" ),
|
| 2168 | value: Cow::Borrowed(b"" ),
|
| 2169 | }))
|
| 2170 | );
|
| 2171 | assert_eq!(iter.next(), None);
|
| 2172 | assert_eq!(iter.next(), None);
|
| 2173 | }
|
| 2174 |
|
| 2175 | /// Only attribute key is present
|
| 2176 | #[test ]
|
| 2177 | fn key_only() {
|
| 2178 | let mut iter = Attributes::html(r#"tag key='value' key another=''"# , 3);
|
| 2179 | iter.with_checks(false);
|
| 2180 |
|
| 2181 | assert_eq!(
|
| 2182 | iter.next(),
|
| 2183 | Some(Ok(Attribute {
|
| 2184 | key: QName(b"key" ),
|
| 2185 | value: Cow::Borrowed(b"value" ),
|
| 2186 | }))
|
| 2187 | );
|
| 2188 | assert_eq!(
|
| 2189 | iter.next(),
|
| 2190 | Some(Ok(Attribute {
|
| 2191 | key: QName(b"key" ),
|
| 2192 | value: Cow::Borrowed(&[]),
|
| 2193 | }))
|
| 2194 | );
|
| 2195 | assert_eq!(
|
| 2196 | iter.next(),
|
| 2197 | Some(Ok(Attribute {
|
| 2198 | key: QName(b"another" ),
|
| 2199 | value: Cow::Borrowed(b"" ),
|
| 2200 | }))
|
| 2201 | );
|
| 2202 | assert_eq!(iter.next(), None);
|
| 2203 | assert_eq!(iter.next(), None);
|
| 2204 | }
|
| 2205 | }
|
| 2206 | }
|
| 2207 |
|
| 2208 | #[test ]
|
| 2209 | fn mixed_quote() {
|
| 2210 | let mut iter = Attributes::html(r#"tag a='a' b = "b" c='cc"cc' d="dd'dd""# , 3);
|
| 2211 |
|
| 2212 | assert_eq!(
|
| 2213 | iter.next(),
|
| 2214 | Some(Ok(Attribute {
|
| 2215 | key: QName(b"a" ),
|
| 2216 | value: Cow::Borrowed(b"a" ),
|
| 2217 | }))
|
| 2218 | );
|
| 2219 | assert_eq!(
|
| 2220 | iter.next(),
|
| 2221 | Some(Ok(Attribute {
|
| 2222 | key: QName(b"b" ),
|
| 2223 | value: Cow::Borrowed(b"b" ),
|
| 2224 | }))
|
| 2225 | );
|
| 2226 | assert_eq!(
|
| 2227 | iter.next(),
|
| 2228 | Some(Ok(Attribute {
|
| 2229 | key: QName(b"c" ),
|
| 2230 | value: Cow::Borrowed(br#"cc"cc"# ),
|
| 2231 | }))
|
| 2232 | );
|
| 2233 | assert_eq!(
|
| 2234 | iter.next(),
|
| 2235 | Some(Ok(Attribute {
|
| 2236 | key: QName(b"d" ),
|
| 2237 | value: Cow::Borrowed(b"dd'dd" ),
|
| 2238 | }))
|
| 2239 | );
|
| 2240 | assert_eq!(iter.next(), None);
|
| 2241 | assert_eq!(iter.next(), None);
|
| 2242 | }
|
| 2243 | }
|
| 2244 | |