use core::ops::Range;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SourcePosition {
pub line: u32,
pub offset: u32,
}
impl SourcePosition {
#[inline]
pub(crate) fn offset_usize(self) -> usize {
self.offset as usize
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SourceSpan {
pub start: SourcePosition,
pub end: SourcePosition,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ElementData {
pub(crate) tag: String,
pub(crate) attrs: Vec<(String, String)>,
pub(crate) content_range: Range<usize>,
pub(crate) children: Vec<ElementData>,
pub(crate) span: SourceSpan,
pub(crate) self_closing: bool,
}
#[derive(Debug, Clone, Copy)]
pub struct ElementRef<'a> {
pub(crate) data: &'a ElementData,
pub(crate) raw: &'a str,
pub(crate) trivia: &'a [core::ops::Range<usize>],
}
impl<'a> ElementRef<'a> {
#[must_use]
pub fn tag(&self) -> &'a str {
&self.data.tag
}
#[must_use]
pub fn attr(&self, name: &str) -> Option<&'a str> {
self.data
.attrs
.iter()
.find(|(k, _)| k == name)
.map(|(_, v)| v.as_str())
}
pub fn attrs(&self) -> impl Iterator<Item = (&'a str, &'a str)> + 'a {
self.data
.attrs
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
}
#[must_use]
pub fn content(&self) -> &'a str {
&self.raw[self.data.content_range.clone()]
}
pub(crate) fn content_range(&self) -> core::ops::Range<usize> {
self.data.content_range.clone()
}
pub fn children(&self) -> impl Iterator<Item = ElementRef<'a>> + 'a {
let raw = self.raw;
let trivia = self.trivia;
self.data.children.iter().map(move |child| ElementRef {
data: child,
raw,
trivia,
})
}
#[must_use]
pub fn location(&self) -> SourceSpan {
self.data.span
}
#[must_use]
pub fn is_self_closing(&self) -> bool {
self.data.self_closing
}
pub fn select(&self, sel: &crate::Selector) -> impl Iterator<Item = ElementRef<'a>> + 'a {
crate::selector::select(&self.data.children, self.raw, self.trivia, sel).into_iter()
}
pub fn text(&self) -> impl Iterator<Item = &'a str> + 'a {
TextSegments::new_with_trivia(self.raw, self.data, self.trivia)
}
}
#[derive(Debug, Clone)]
pub(crate) struct TextSegments<'a> {
raw: &'a str,
cursor: usize,
end: usize,
children: core::slice::Iter<'a, ElementData>,
trivia: &'a [core::ops::Range<usize>],
trivia_idx: usize,
}
impl<'a> TextSegments<'a> {
pub(crate) fn new_with_trivia(
raw: &'a str,
data: &'a ElementData,
trivia: &'a [core::ops::Range<usize>],
) -> Self {
let start = data.content_range.start;
let trivia_idx = trivia.partition_point(|r| r.end <= start);
Self {
raw,
cursor: start,
end: data.content_range.end,
children: data.children.iter(),
trivia,
trivia_idx,
}
}
}
impl<'a> Iterator for TextSegments<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
loop {
let child_next = self.children.as_slice().first().map(|c| {
let s = c.span.start.offset_usize();
let e = c.span.end.offset_usize();
s..e
});
while self.trivia_idx < self.trivia.len()
&& self.trivia[self.trivia_idx].end <= self.cursor
{
self.trivia_idx += 1;
}
let trivia_next = self.trivia.get(self.trivia_idx).and_then(|r| {
if r.start >= self.end {
None
} else {
Some(r.clone())
}
});
let pick = match (child_next, trivia_next) {
(Some(c), Some(t)) if c.start <= t.start => {
self.children.next();
Some(c)
}
(Some(c), None) => {
self.children.next();
Some(c)
}
(_, Some(t)) => {
self.trivia_idx += 1;
Some(t)
}
(None, None) => None,
};
let Some(span) = pick else {
if self.cursor < self.end {
let segment = &self.raw[self.cursor..self.end];
self.cursor = self.end;
return Some(segment);
}
return None;
};
let seg_end = span.start.min(self.end).max(self.cursor);
let segment = &self.raw[self.cursor..seg_end];
self.cursor = span.end.max(self.cursor).min(self.end);
if !segment.is_empty() {
return Some(segment);
}
}
}
}