use super::super::super::text::{RetainedFilterChain, RetainedTextBinding};
use super::super::TextRefusal;
use super::ComponentExpressionPool;
use crate::markup::{Element, ElementClose, SurfaceChild};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RetainedTextRefusal {
Body(TextRefusal),
ExpressionHandoff,
}
impl From<TextRefusal> for RetainedTextRefusal {
fn from(refusal: TextRefusal) -> Self {
Self::Body(refusal)
}
}
pub struct RetainedTextChildren<'o, 'a> {
component: &'o ComponentExpressionPool<'a>,
parent: Option<&'o Element<'a>>,
recovered: bool,
children: core::iter::Enumerate<core::slice::Iter<'o, SurfaceChild<'a>>>,
}
impl<'o, 'a> Iterator for RetainedTextChildren<'o, 'a> {
type Item = RetainedTextChild<'o, 'a>;
fn next(&mut self) -> Option<Self::Item> {
let (ordinal, surface) = self.children.next()?;
Some(RetainedTextChild {
component: self.component,
parent: self.parent,
recovered: self.recovered,
ordinal,
surface,
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.children.size_hint()
}
}
impl ExactSizeIterator for RetainedTextChildren<'_, '_> {}
#[derive(Debug)]
pub struct RetainedTextChild<'o, 'a> {
component: &'o ComponentExpressionPool<'a>,
parent: Option<&'o Element<'a>>,
recovered: bool,
ordinal: usize,
surface: &'o SurfaceChild<'a>,
}
impl<'o, 'a> RetainedTextChild<'o, 'a> {
pub fn reborrow(&self) -> RetainedTextChild<'_, 'a> {
RetainedTextChild {
component: self.component,
parent: self.parent,
recovered: self.recovered,
ordinal: self.ordinal,
surface: self.surface,
}
}
pub fn component(&self) -> &'o ComponentExpressionPool<'a> {
self.component
}
pub fn parent_element(&self) -> Option<&'o Element<'a>> {
self.parent
}
pub const fn ordinal(&self) -> usize {
self.ordinal
}
pub fn surface(&self) -> &'o SurfaceChild<'a> {
self.surface
}
pub fn children(&self) -> Option<RetainedTextChildren<'o, 'a>> {
let SurfaceChild::Element(element) = self.surface else {
return None;
};
Some(RetainedTextChildren {
component: self.component,
parent: Some(element),
recovered: self.recovered || recovered(element),
children: element.children.iter().enumerate(),
})
}
}
impl<'a> ComponentExpressionPool<'a> {
pub fn children(&self) -> RetainedTextChildren<'_, 'a> {
RetainedTextChildren {
component: self,
parent: None,
recovered: false,
children: self.tree.children.iter().enumerate(),
}
}
pub fn authored_children(&self) -> Option<RetainedTextChildren<'_, 'a>> {
Some(RetainedTextChildren {
component: self,
parent: None,
recovered: true,
children: self.authored.as_ref()?.children.iter().enumerate(),
})
}
pub fn text_for<'o>(
&'o self,
child: RetainedTextChild<'o, 'a>,
) -> Result<RetainedTextView<'o, 'a>, RetainedTextRefusal> {
if !core::ptr::eq(self, child.component) {
return Err(TextRefusal::ForeignComponent.into());
}
let SurfaceChild::Interpolation(node) = child.surface else {
return Err(TextRefusal::NotInterpolation.into());
};
if !self.errors.is_empty() || child.recovered {
return Err(TextRefusal::RecoveredComponent.into());
}
if child.parent.is_some_and(|parent| parent.open.is_verbatim()) {
return Err(TextRefusal::Verbatim.into());
}
if let Some(boundary) = self.unsupported.first() {
return Err(TextRefusal::Boundary(boundary.kind).into());
}
let start = self
.block
.offset_of(node.open.text)
.ok_or(TextRefusal::UnmatchedCallback)?;
let index = self
.bindings
.binary_search_by_key(&start, |binding| binding.span().start)
.map_err(|_| TextRefusal::UnmatchedCallback)?;
let binding = self
.bindings
.get(index)
.ok_or(TextRefusal::UnmatchedCallback)?;
if let Some(boundary) = binding.boundaries().first() {
return Err(TextRefusal::Boundary(boundary.kind).into());
}
let open = self.block.span_of(node.open.text);
let content = self.block.span_of(node.content.text);
let close = self.block.span_of(node.close.text);
if node.is_raw_html()
|| node.open.is_missing()
|| node.content.is_missing()
|| node.close.is_missing()
|| node.open.text != "{{"
|| node.close.text != "}}"
|| !binding.matches(self.block, node.content.text)
|| open.map(|span| span.start) != Some(binding.span().start)
|| close.map(|span| span.end) != Some(binding.span().end)
|| open.map(|span| span.end) != content.map(|span| span.start)
|| content.map(|span| span.end) != close.map(|span| span.start)
{
return Err(TextRefusal::UnmatchedCallback.into());
}
let chain = if let Some(chain) = binding.admitted() {
chain
} else {
let chain = binding.chain().ok_or(TextRefusal::UnmatchedCallback)?;
if chain.base().original().is_some()
|| chain
.filters()
.iter()
.flat_map(|filter| filter.arguments())
.any(|argument| argument.original().is_some())
{
return Err(RetainedTextRefusal::ExpressionHandoff);
}
let hole = chain.base().hole().or_else(|| {
chain
.filters()
.iter()
.flat_map(|filter| filter.arguments())
.find_map(|arg| arg.hole())
});
return Err(hole
.map_or(TextRefusal::UnmatchedCallback, TextRefusal::NativeHole)
.into());
};
Ok(RetainedTextView {
binding,
child,
chain,
})
}
}
#[derive(Debug)]
pub struct RetainedTextView<'o, 'a> {
binding: &'o RetainedTextBinding<'a>,
child: RetainedTextChild<'o, 'a>,
chain: &'o RetainedFilterChain<'a>,
}
impl<'o, 'a> RetainedTextView<'o, 'a> {
pub fn binding(&self) -> &'o RetainedTextBinding<'a> {
self.binding
}
pub fn child(&self) -> &RetainedTextChild<'o, 'a> {
&self.child
}
pub fn chain(&self) -> &'o RetainedFilterChain<'a> {
self.chain
}
}
fn recovered(element: &Element<'_>) -> bool {
element.open.lt_name.is_missing()
|| element.open.gt.is_missing()
|| element
.open
.slash
.as_ref()
.is_some_and(|token| token.is_missing())
|| match &element.close {
ElementClose::Present(close) => {
close.lt_slash_name.is_missing() || close.gt.is_missing()
}
ElementClose::Missing | ElementClose::Implicit => true,
ElementClose::NotExpected => false,
}
}
#[cfg(test)]
mod tests;