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