use std::slice::Iter;
use crate::{
HasSpan, Parser, Span,
attributes::Attrlist,
blocks::{
Break, CompoundDelimitedBlock, ContentModel, IsBlock, ListBlock, ListItem, ListItemMarker,
MediaBlock, Preamble, RawDelimitedBlock, SectionBlock, SimpleBlock,
metadata::BlockMetadata,
},
content::{Content, SubstitutionGroup},
document::{Attribute, RefType},
parser::{InlineSubstitutionRenderer, ReferenceResolver, ReferenceWarning},
span::MatchedItem,
strings::CowStr,
warnings::{MatchAndWarnings, Warning, WarningType},
};
#[derive(Clone, Eq, PartialEq)]
#[allow(clippy::large_enum_variant)] #[non_exhaustive]
pub enum Block<'src> {
Simple(SimpleBlock<'src>),
Media(MediaBlock<'src>),
Section(SectionBlock<'src>),
List(ListBlock<'src>),
ListItem(ListItem<'src>),
RawDelimited(RawDelimitedBlock<'src>),
CompoundDelimited(CompoundDelimitedBlock<'src>),
Preamble(Preamble<'src>),
Break(Break<'src>),
DocumentAttribute(Attribute<'src>),
}
impl<'src> std::fmt::Debug for Block<'src> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Block::Simple(block) => f.debug_tuple("Block::Simple").field(block).finish(),
Block::Media(block) => f.debug_tuple("Block::Media").field(block).finish(),
Block::Section(block) => f.debug_tuple("Block::Section").field(block).finish(),
Block::List(block) => f.debug_tuple("Block::List").field(block).finish(),
Block::ListItem(block) => f.debug_tuple("Block::ListItem").field(block).finish(),
Block::RawDelimited(block) => {
f.debug_tuple("Block::RawDelimited").field(block).finish()
}
Block::CompoundDelimited(block) => f
.debug_tuple("Block::CompoundDelimited")
.field(block)
.finish(),
Block::Preamble(block) => f.debug_tuple("Block::Preamble").field(block).finish(),
Block::Break(break_) => f.debug_tuple("Block::Break").field(break_).finish(),
Block::DocumentAttribute(block) => f
.debug_tuple("Block::DocumentAttribute")
.field(block)
.finish(),
}
}
}
impl<'src> Block<'src> {
pub(crate) fn parse(
source: Span<'src>,
parser: &mut Parser,
) -> MatchAndWarnings<'src, Option<MatchedItem<'src, Self>>> {
Self::parse_internal(source, parser, None, false)
}
pub(crate) fn parse_for_list_item(
source: Span<'src>,
parser: &mut Parser,
parent_list_markers: &[ListItemMarker<'src>],
is_continuation: bool,
) -> MatchAndWarnings<'src, Option<MatchedItem<'src, Self>>> {
Self::parse_internal(source, parser, Some(parent_list_markers), is_continuation)
}
fn parse_internal(
source: Span<'src>,
parser: &mut Parser,
parent_list_markers: Option<&[ListItemMarker<'src>]>,
is_continuation: bool,
) -> MatchAndWarnings<'src, Option<MatchedItem<'src, Self>>> {
let first_line = source.take_line().item.discard_whitespace();
if let Some(first_char) = first_line.chars().next()
&& !matches!(
first_char,
'.' | '#' | '=' | '/' | '-' | '+' | '*' | '_' | '[' | ':' | '\'' | '<' | '•'
)
&& !first_line.contains("::")
&& !first_line.contains(";;")
&& !ListItemMarker::starts_with_marker(first_line)
&& parent_list_markers.is_none()
&& let Some(MatchedItem {
item: simple_block,
after,
}) = SimpleBlock::parse_fast(source, parser)
{
let mut warnings = vec![];
let block = Self::Simple(simple_block);
Self::register_block_id(
block.id(),
block.title(),
block.span(),
parser,
&mut warnings,
);
return MatchAndWarnings {
item: Some(MatchedItem { item: block, after }),
warnings,
};
}
if first_line.starts_with(':')
&& (first_line.ends_with(':') || first_line.contains(": "))
&& let Some(attr) = Attribute::parse(source, parser)
{
let mut warnings: Vec<Warning<'src>> = vec![];
parser.set_attribute_from_body(&attr.item, &mut warnings);
return MatchAndWarnings {
item: Some(MatchedItem {
item: Self::DocumentAttribute(attr.item),
after: attr.after,
}),
warnings,
};
}
let MatchAndWarnings {
item: mut metadata,
mut warnings,
} = BlockMetadata::parse(source, parser);
let is_literal =
metadata.attrlist.as_ref().and_then(|a| a.block_style()) == Some("literal");
if !is_literal {
if let Some(mut rdb_maw) = RawDelimitedBlock::parse(&metadata, parser)
&& let Some(rdb) = rdb_maw.item
{
if !rdb_maw.warnings.is_empty() {
warnings.append(&mut rdb_maw.warnings);
}
let block = Self::RawDelimited(rdb.item);
Self::register_block_id(
block.id(),
block.title(),
block.span(),
parser,
&mut warnings,
);
return MatchAndWarnings {
item: Some(MatchedItem {
item: block,
after: rdb.after,
}),
warnings,
};
}
if let Some(mut cdb_maw) = CompoundDelimitedBlock::parse(&metadata, parser)
&& let Some(cdb) = cdb_maw.item
{
if !cdb_maw.warnings.is_empty() {
warnings.append(&mut cdb_maw.warnings);
}
let block = Self::CompoundDelimited(cdb.item);
Self::register_block_id(
block.id(),
block.title(),
block.span(),
parser,
&mut warnings,
);
return MatchAndWarnings {
item: Some(MatchedItem {
item: block,
after: cdb.after,
}),
warnings,
};
}
let line = metadata.block_start.take_normalized_line();
if line.item.starts_with("image::")
|| line.item.starts_with("video::")
|| line.item.starts_with("video::")
{
let mut media_block_maw = MediaBlock::parse(&metadata, parser);
if let Some(media_block) = media_block_maw.item {
if !media_block_maw.warnings.is_empty() {
warnings.append(&mut media_block_maw.warnings);
}
let block = Self::Media(media_block.item);
Self::register_block_id(
block.id(),
block.title(),
block.span(),
parser,
&mut warnings,
);
return MatchAndWarnings {
item: Some(MatchedItem {
item: block,
after: media_block.after,
}),
warnings,
};
}
}
if (line.item.starts_with('=') || line.item.starts_with('#'))
&& let Some(mi_section_block) =
SectionBlock::parse(&metadata, parser, &mut warnings)
{
return MatchAndWarnings {
item: Some(MatchedItem {
item: Self::Section(mi_section_block.item),
after: mi_section_block.after,
}),
warnings,
};
}
if (line.item.starts_with('\'')
|| line.item.starts_with('-')
|| line.item.starts_with('*')
|| line.item.starts_with('<'))
&& let Some(mi_break) = Break::parse(&metadata, parser)
{
return MatchAndWarnings {
item: Some(MatchedItem {
item: Self::Break(mi_break.item),
after: mi_break.after,
}),
warnings,
};
}
if parent_list_markers.is_none()
&& let Some(mi_list) = ListBlock::parse(&metadata, parser, &mut warnings)
{
return MatchAndWarnings {
item: Some(MatchedItem {
item: Self::List(mi_list.item),
after: mi_list.after,
}),
warnings,
};
}
let simple_block_mi = if let Some(plm) = parent_list_markers {
SimpleBlock::parse_for_list_item(&metadata, parser, is_continuation, plm)
} else {
SimpleBlock::parse(&metadata, parser)
};
if simple_block_mi.is_none() && !metadata.is_empty() {
warnings.push(Warning {
source: metadata.source,
warning: WarningType::MissingBlockAfterTitleOrAttributeList,
});
metadata.title_source = None;
metadata.title = None;
metadata.anchor = None;
metadata.attrlist = None;
metadata.block_start = metadata.source;
}
}
let simple_block_mi = if let Some(plm) = parent_list_markers {
SimpleBlock::parse_for_list_item(&metadata, parser, is_continuation, plm)
} else {
SimpleBlock::parse(&metadata, parser)
};
let mut result = MatchAndWarnings {
item: simple_block_mi.map(|mi| MatchedItem {
item: Self::Simple(mi.item),
after: mi.after,
}),
warnings,
};
if let Some(ref matched_item) = result.item {
Self::register_block_id(
matched_item.item.id(),
matched_item.item.title(),
matched_item.item.span(),
parser,
&mut result.warnings,
);
}
result
}
fn register_block_id(
id: Option<&str>,
title: Option<&str>,
span: Span<'src>,
parser: &mut Parser,
warnings: &mut Vec<Warning<'src>>,
) {
if let Some(id) = id
&& let Err(_duplicate_error) = parser.register_ref(
id,
title, RefType::Anchor,
)
{
warnings.push(Warning {
source: span,
warning: WarningType::DuplicateId(id.to_string()),
});
}
}
pub(crate) fn as_list_item(&self) -> Option<&ListItem<'src>> {
match self {
Self::ListItem(li) => Some(li),
_ => None,
}
}
pub(crate) fn resolve_references(
&mut self,
resolver: &dyn ReferenceResolver,
renderer: &dyn InlineSubstitutionRenderer,
warnings: &mut Vec<ReferenceWarning>,
) {
if let Some(content) = self.content_mut() {
content.resolve_references(resolver, renderer, warnings);
}
for child in self.nested_blocks_mut() {
child.resolve_references(resolver, renderer, warnings);
}
}
}
impl<'src> IsBlock<'src> for Block<'src> {
fn content_model(&self) -> ContentModel {
match self {
Self::Simple(_) => ContentModel::Simple,
Self::Media(b) => b.content_model(),
Self::Section(_) => ContentModel::Compound,
Self::List(b) => b.content_model(),
Self::ListItem(b) => b.content_model(),
Self::RawDelimited(b) => b.content_model(),
Self::CompoundDelimited(b) => b.content_model(),
Self::Preamble(b) => b.content_model(),
Self::Break(b) => b.content_model(),
Self::DocumentAttribute(b) => b.content_model(),
}
}
fn rendered_content(&'src self) -> Option<&'src str> {
match self {
Self::Simple(b) => b.rendered_content(),
Self::Media(b) => b.rendered_content(),
Self::Section(b) => b.rendered_content(),
Self::List(b) => b.rendered_content(),
Self::ListItem(b) => b.rendered_content(),
Self::RawDelimited(b) => b.rendered_content(),
Self::CompoundDelimited(b) => b.rendered_content(),
Self::Preamble(b) => b.rendered_content(),
Self::Break(b) => b.rendered_content(),
Self::DocumentAttribute(b) => b.rendered_content(),
}
}
fn raw_context(&self) -> CowStr<'src> {
match self {
Self::Simple(b) => b.raw_context(),
Self::Media(b) => b.raw_context(),
Self::Section(b) => b.raw_context(),
Self::List(b) => b.raw_context(),
Self::ListItem(b) => b.raw_context(),
Self::RawDelimited(b) => b.raw_context(),
Self::CompoundDelimited(b) => b.raw_context(),
Self::Preamble(b) => b.raw_context(),
Self::Break(b) => b.raw_context(),
Self::DocumentAttribute(b) => b.raw_context(),
}
}
fn nested_blocks(&'src self) -> Iter<'src, Block<'src>> {
match self {
Self::Simple(b) => b.nested_blocks(),
Self::Media(b) => b.nested_blocks(),
Self::Section(b) => b.nested_blocks(),
Self::List(b) => b.nested_blocks(),
Self::ListItem(b) => b.nested_blocks(),
Self::RawDelimited(b) => b.nested_blocks(),
Self::CompoundDelimited(b) => b.nested_blocks(),
Self::Preamble(b) => b.nested_blocks(),
Self::Break(b) => b.nested_blocks(),
Self::DocumentAttribute(b) => b.nested_blocks(),
}
}
fn nested_blocks_mut(&mut self) -> &mut [Block<'src>] {
match self {
Self::Simple(b) => b.nested_blocks_mut(),
Self::Media(b) => b.nested_blocks_mut(),
Self::Section(b) => b.nested_blocks_mut(),
Self::List(b) => b.nested_blocks_mut(),
Self::ListItem(b) => b.nested_blocks_mut(),
Self::RawDelimited(b) => b.nested_blocks_mut(),
Self::CompoundDelimited(b) => b.nested_blocks_mut(),
Self::Preamble(b) => b.nested_blocks_mut(),
Self::Break(b) => b.nested_blocks_mut(),
Self::DocumentAttribute(b) => b.nested_blocks_mut(),
}
}
fn content_mut(&mut self) -> Option<&mut Content<'src>> {
match self {
Self::Simple(b) => b.content_mut(),
Self::Media(b) => b.content_mut(),
Self::Section(b) => b.content_mut(),
Self::List(b) => b.content_mut(),
Self::ListItem(b) => b.content_mut(),
Self::RawDelimited(b) => b.content_mut(),
Self::CompoundDelimited(b) => b.content_mut(),
Self::Preamble(b) => b.content_mut(),
Self::Break(b) => b.content_mut(),
Self::DocumentAttribute(b) => b.content_mut(),
}
}
fn title_source(&'src self) -> Option<Span<'src>> {
match self {
Self::Simple(b) => b.title_source(),
Self::Media(b) => b.title_source(),
Self::Section(b) => b.title_source(),
Self::List(b) => b.title_source(),
Self::ListItem(b) => b.title_source(),
Self::RawDelimited(b) => b.title_source(),
Self::CompoundDelimited(b) => b.title_source(),
Self::Preamble(b) => b.title_source(),
Self::Break(b) => b.title_source(),
Self::DocumentAttribute(b) => b.title_source(),
}
}
fn title(&self) -> Option<&str> {
match self {
Self::Simple(b) => b.title(),
Self::Media(b) => b.title(),
Self::Section(b) => b.title(),
Self::List(b) => b.title(),
Self::ListItem(b) => b.title(),
Self::RawDelimited(b) => b.title(),
Self::CompoundDelimited(b) => b.title(),
Self::Preamble(b) => b.title(),
Self::Break(b) => b.title(),
Self::DocumentAttribute(b) => b.title(),
}
}
fn anchor(&'src self) -> Option<Span<'src>> {
match self {
Self::Simple(b) => b.anchor(),
Self::Media(b) => b.anchor(),
Self::Section(b) => b.anchor(),
Self::List(b) => b.anchor(),
Self::ListItem(b) => b.anchor(),
Self::RawDelimited(b) => b.anchor(),
Self::CompoundDelimited(b) => b.anchor(),
Self::Preamble(b) => b.anchor(),
Self::Break(b) => b.anchor(),
Self::DocumentAttribute(b) => b.anchor(),
}
}
fn anchor_reftext(&'src self) -> Option<Span<'src>> {
match self {
Self::Simple(b) => b.anchor_reftext(),
Self::Media(b) => b.anchor_reftext(),
Self::Section(b) => b.anchor_reftext(),
Self::List(b) => b.anchor_reftext(),
Self::ListItem(b) => b.anchor_reftext(),
Self::RawDelimited(b) => b.anchor_reftext(),
Self::CompoundDelimited(b) => b.anchor_reftext(),
Self::Preamble(b) => b.anchor_reftext(),
Self::Break(b) => b.anchor_reftext(),
Self::DocumentAttribute(b) => b.anchor_reftext(),
}
}
fn attrlist(&'src self) -> Option<&'src Attrlist<'src>> {
match self {
Self::Simple(b) => b.attrlist(),
Self::Media(b) => b.attrlist(),
Self::Section(b) => b.attrlist(),
Self::List(b) => b.attrlist(),
Self::ListItem(b) => b.attrlist(),
Self::RawDelimited(b) => b.attrlist(),
Self::CompoundDelimited(b) => b.attrlist(),
Self::Preamble(b) => b.attrlist(),
Self::Break(b) => b.attrlist(),
Self::DocumentAttribute(b) => b.attrlist(),
}
}
fn substitution_group(&self) -> SubstitutionGroup {
match self {
Self::Simple(b) => b.substitution_group(),
Self::Media(b) => b.substitution_group(),
Self::Section(b) => b.substitution_group(),
Self::List(b) => b.substitution_group(),
Self::ListItem(b) => b.substitution_group(),
Self::RawDelimited(b) => b.substitution_group(),
Self::CompoundDelimited(b) => b.substitution_group(),
Self::Preamble(b) => b.substitution_group(),
Self::Break(b) => b.substitution_group(),
Self::DocumentAttribute(b) => b.substitution_group(),
}
}
}
impl<'src> HasSpan<'src> for Block<'src> {
fn span(&self) -> Span<'src> {
match self {
Self::Simple(b) => b.span(),
Self::Media(b) => b.span(),
Self::Section(b) => b.span(),
Self::List(b) => b.span(),
Self::ListItem(b) => b.span(),
Self::RawDelimited(b) => b.span(),
Self::CompoundDelimited(b) => b.span(),
Self::Preamble(b) => b.span(),
Self::Break(b) => b.span(),
Self::DocumentAttribute(b) => b.span(),
}
}
}