use super::{
ContentBlockStep, ContentInlineRoot, ContentLocation, ContentLocationRef, MAX_CONTENT_DEPTH,
};
use crate::{Block, Document, Inline, Section};
impl ContentLocation {
#[must_use]
pub fn resolve<'a>(&self, document: &'a Document) -> Option<&'a [Inline]> {
self.as_ref().resolve(document)
}
#[must_use]
pub fn resolve_link<'a>(&self, document: &'a Document) -> Option<&'a Inline> {
self.as_ref().resolve_link(document)
}
}
impl ContentLocationRef<'_> {
#[must_use]
pub fn resolve(self, document: &Document) -> Option<&[Inline]> {
if !self.within_limits() {
return None;
}
let (nodes, path) = match self {
Self::DocumentHeading { path } => (document.heading.as_ref()?.content.as_slice(), path),
Self::SectionHeading { sections, path } => (
resolve_content_section(document, sections)?
.heading
.content
.as_slice(),
path,
),
Self::Content {
sections,
blocks,
root,
path,
} => {
let blocks_root = content_blocks(document, sections)?;
let block = resolve_content_block(blocks_root, blocks)?;
(resolve_inline_root(block, root)?, path)
}
};
resolve_inline_path(nodes, path)
}
#[must_use]
pub fn resolve_link(self, document: &Document) -> Option<&Inline> {
let path = match self {
Self::DocumentHeading { path }
| Self::SectionHeading { path, .. }
| Self::Content { path, .. } => path,
};
if path.is_empty() {
return None;
}
match self.resolve(document)? {
[link @ Inline::Link { .. }] => Some(link),
_ => None,
}
}
}
#[must_use]
pub fn resolve_content_section<'a>(document: &'a Document, path: &[u32]) -> Option<&'a Section> {
if path.is_empty() || path.len() > MAX_CONTENT_DEPTH {
return None;
}
let mut children = document.sections.as_slice();
let mut selected = None;
for index in path {
let section = children.get(*index as usize)?;
selected = Some(section);
children = §ion.children;
}
selected
}
pub(crate) fn content_blocks<'a>(document: &'a Document, sections: &[u32]) -> Option<&'a [Block]> {
if sections.is_empty() {
Some(&document.blocks)
} else {
Some(&resolve_content_section(document, sections)?.blocks)
}
}
#[must_use]
pub fn resolve_content_block<'a>(
blocks: &'a [Block],
path: &[ContentBlockStep],
) -> Option<&'a Block> {
let (ContentBlockStep::Block { index }, rest) = path.split_first()? else {
return None;
};
if path.len() > MAX_CONTENT_DEPTH {
return None;
}
resolve_block_descendant(blocks.get(*index as usize)?, rest)
}
#[must_use]
pub fn resolve_block_descendant<'a>(
mut block: &'a Block,
path: &[ContentBlockStep],
) -> Option<&'a Block> {
if path.len() > MAX_CONTENT_DEPTH * 2 + 2 || !path.len().is_multiple_of(2) {
return None;
}
for pair in path.as_chunks::<2>().0 {
let children = block_children(block, pair[0])?;
let ContentBlockStep::Block { index } = pair[1] else {
return None;
};
block = children.get(index as usize)?;
}
Some(block)
}
pub(crate) fn block_children(block: &Block, step: ContentBlockStep) -> Option<&[Block]> {
match (block, step) {
(Block::List { items, .. }, ContentBlockStep::ListItem { index }) => {
Some(&items.get(index as usize)?.blocks)
}
(Block::DefinitionList { items, .. }, ContentBlockStep::DefinitionItem { index }) => {
Some(&items.get(index as usize)?.description)
}
(Block::Table { rows, .. }, ContentBlockStep::TableCell { row, column }) => {
Some(&rows.get(row as usize)?.cells.get(column as usize)?.blocks)
}
_ => None,
}
}
fn resolve_inline_root(block: &Block, root: ContentInlineRoot) -> Option<&[Inline]> {
match (block, root) {
(
Block::Paragraph { children, .. } | Block::Preformatted { children, .. },
ContentInlineRoot::Inlines,
) => Some(children),
(
Block::DefinitionList { items, .. },
ContentInlineRoot::DefinitionTerm {
item_index,
term_index,
},
) => Some(
items
.get(item_index as usize)?
.terms
.get(term_index as usize)?,
),
_ => None,
}
}
#[must_use]
pub fn resolve_inline_path<'a>(mut nodes: &'a [Inline], path: &[u32]) -> Option<&'a [Inline]> {
if path.len() > MAX_CONTENT_DEPTH {
return None;
}
for (depth, index) in path.iter().enumerate() {
let node = nodes.get(*index as usize)?;
nodes = if depth + 1 == path.len() {
std::slice::from_ref(node)
} else {
inline_children(node)?
};
}
Some(nodes)
}
pub(crate) fn inline_children(node: &Inline) -> Option<&[Inline]> {
match node {
Inline::Strong { children }
| Inline::Emphasis { children }
| Inline::Link { children, .. } => Some(children),
_ => None,
}
}