xhtml_parser 0.2.10

Non-validating XHTML Tree-based parser.
Documentation
//! Attribute handling for XML/XHTML parsing.
//!
//! This module provides structures and functionality for working with XML attributes,
//! including individual attribute access and iteration over collections of attributes.

use std::fmt::{self, Debug};

use crate::defs::XmlLocation;
use crate::document::Document;
use crate::node::Node;
use crate::node_type::NodeType;

#[cfg(feature = "use_cstr")]
use std::ffi::CStr;

/// Information about an XML attribute, storing name and value ranges within the document.
///
/// This struct holds references to positions in the source document where the attribute
/// name and value are located, allowing for efficient string retrieval without copying.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AttributeInfo {
    name: XmlLocation,
    value: XmlLocation,
}

impl AttributeInfo {
    /// Creates a new `AttributeInfo` with the specified name and value ranges.
    ///
    /// # Arguments
    /// * `name` - The range in the document containing the attribute name
    /// * `value` - The range in the document containing the attribute value
    ///
    /// # Returns
    /// A new `AttributeInfo` instance
    pub(crate) fn new(name: XmlLocation, value: XmlLocation) -> Self {
        AttributeInfo { name, value }
    }
}

/// A reference to an XML attribute within a document.
///
/// This struct provides access to an attribute's name and value by combining
/// a reference to the document with attribute information.
pub struct Attribute<'a> {
    doc: &'a Document,
    data: &'a AttributeInfo,
}

impl<'xml> Attribute<'xml> {
    /// Returns the name of the attribute as a string slice.
    ///
    /// # Returns
    /// A string slice containing the attribute name
    #[inline]
    #[must_use]
    pub fn name(&self) -> &str {
        #[cfg(feature = "use_cstr")]
        {
            self.doc.get_str_from_location(self.data.name)
        }
        #[cfg(not(feature = "use_cstr"))]
        self.doc.get_str_from_location(self.data.name.clone())
    }

    /// Returns the name of the attribute as a byte slice.
    ///
    /// # Returns
    /// A byte slice containing the attribute name
    #[inline]
    #[must_use]
    pub fn name_bytes(&self) -> &[u8] {
        #[cfg(feature = "use_cstr")]
        {
            self.doc.get_cstr_from_location(self.data.name).to_bytes()
        }

        #[cfg(not(feature = "use_cstr"))]
        &self.doc.xml[self.data.name.start as usize..self.data.name.end as usize]
    }

    #[cfg(feature = "use_cstr")]
    /// Returns the name of the attribute as a CStr.
    ///
    /// # Returns
    /// A CStr reference containing the attribute name
    ///
    /// # Note
    /// This method is only available when the `use_cstr` feature is enabled
    #[inline]
    #[must_use]
    pub fn name_cstr(&self) -> &CStr {
        self.doc.get_cstr_from_location(self.data.name)
    }

    /// Returns true if the attribute's name matches the given string.
    ///
    /// # Arguments
    /// * `name` - The string to compare against the attribute's name
    ///
    /// # Returns
    /// True if the attribute's name matches the string, false otherwise
    #[inline]
    #[must_use]
    pub fn is(&self, name: &str) -> bool {
        self.name() == name
    }

    /// Returns true if the attribute's name matches the given byte slice.
    ///
    /// # Arguments
    /// * `name` - The byte slice to compare against the attribute's name
    ///
    /// # Returns
    /// True if the attribute's name matches the byte slice, false otherwise
    #[inline]
    #[must_use]
    pub fn is_bytes(&self, name: &[u8]) -> bool {
        self.name_bytes() == name
    }

    /// Returns true if the attribute's name matches the given CStr.
    ///
    /// # Arguments
    /// * `name` - The CStr to compare against the attribute's name
    ///
    /// # Returns
    /// True if the attribute's name matches the CStr, false otherwise
    ///  
    /// # Note
    /// This method is only available when the `use_cstr` feature is enabled
    #[cfg(feature = "use_cstr")]
    #[inline]
    #[must_use]
    pub fn is_cstr(&self, name: &CStr) -> bool {
        self.name_cstr() == name
    }

    /// Returns the value of the attribute as a string slice.
    ///
    /// # Returns  
    /// A string slice containing the attribute value
    #[inline]
    #[must_use]
    pub fn value(&self) -> &'xml str {
        #[cfg(feature = "use_cstr")]
        {
            self.doc.get_str_from_location(self.data.value)
        }
        #[cfg(not(feature = "use_cstr"))]
        self.doc.get_str_from_location(self.data.value.clone())
    }

    /// Returns the vale of the attribute as a byte slice.
    ///
    /// # Returns
    /// A byte slice containing the attribute value
    #[inline]
    #[must_use]
    pub fn value_bytes(&self) -> &[u8] {
        #[cfg(feature = "use_cstr")]
        {
            self.doc.get_cstr_from_location(self.data.value).to_bytes()
        }

        #[cfg(not(feature = "use_cstr"))]
        &self.doc.xml[self.data.name.start as usize..self.data.name.end as usize]
    }

    #[cfg(feature = "use_cstr")]
    /// Returns the value of the attribute as a CStr.
    ///
    /// # Returns
    /// A CStr reference containing the attribute value
    ///
    /// # Note
    /// This method is only available when the `use_cstr` feature is enabled
    #[inline]
    #[must_use]
    pub fn value_cstr(&self) -> &'xml CStr {
        self.doc.get_cstr_from_location(self.data.value)
    }
}

/// Custom Debug implementation for Attribute that displays name and value.
impl fmt::Debug for Attribute<'_> {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(
            f,
            "Attribute {{ name: \"{}\", value: \"{}\" }}",
            self.name(),
            self.value()
        )
    }
}

/// An iterator over the attributes of an XML node.
///
/// This iterator provides access to all attributes belonging to a specific node,
/// yielding `Attribute` instances for each attribute found.
#[derive(Clone)]
pub struct Attributes<'a> {
    doc: &'a Document,
    attrs: core::slice::Iter<'a, AttributeInfo>,
}

impl<'a> Attributes<'a> {
    /// Creates a new Attributes iterator for the given node.
    ///
    /// # Arguments
    /// * `node` - The node whose attributes should be iterated over
    ///
    /// # Returns
    /// An Attributes iterator that will yield all attributes of the node
    #[inline]
    #[must_use]
    pub fn new(node: &Node<'a>) -> Attributes<'a> {
        let attrs = match node.node_info.node_type() {
            NodeType::Element { ref attributes, .. } => {
                &node.doc.attributes[attributes.start as usize..attributes.end as usize]
            }
            _ => &[],
        };

        Attributes {
            doc: node.doc,
            attrs: attrs.iter(),
        }
    }
}

/// Iterator implementation that yields Attribute instances.
impl<'a> Iterator for Attributes<'a> {
    type Item = Attribute<'a>;

    /// Returns the next attribute in the iteration.
    ///
    /// # Returns
    /// Some(Attribute) if there are more attributes, None otherwise
    #[inline]
    fn next(&mut self) -> Option<Self::Item> {
        self.attrs.next().map(|attr| Attribute {
            doc: self.doc,
            data: attr,
        })
    }

    /// Returns the nth attribute, skipping n-1 attributes.
    ///
    /// # Arguments
    /// * `n` - The number of attributes to skip
    ///
    /// # Returns
    /// Some(Attribute) if the nth attribute exists, None otherwise
    #[inline]
    fn nth(&mut self, n: usize) -> Option<Self::Item> {
        self.attrs.nth(n).map(|attr| Attribute {
            doc: self.doc,
            data: attr,
        })
    }

    /// Returns bounds on the remaining length of the iterator.
    ///
    /// # Returns
    /// A tuple of (`lower_bound`, `upper_bound`) where `upper_bound` is `Some(exact_size)`
    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) {
        self.attrs.size_hint()
    }
}

/// `DoubleEndedIterator` implementation allowing reverse iteration.
impl DoubleEndedIterator for Attributes<'_> {
    /// Returns the next attribute from the end of the iteration.
    ///
    /// # Returns
    /// Some(Attribute) if there are more attributes from the back, None otherwise
    #[inline]
    fn next_back(&mut self) -> Option<Self::Item> {
        self.attrs.next_back().map(|attr| Attribute {
            doc: self.doc,
            data: attr,
        })
    }
}

/// `ExactSizeIterator` implementation indicating the iterator knows its exact length.
impl ExactSizeIterator for Attributes<'_> {
    // #[inline]
    // fn len(&self) -> usize {
    //     self.attrs.len()
    // }
}