use std::borrow::Cow;
use crate::{
BlockMetadata, ProcessedContent, Title,
grammar::{ParserState, passthrough_processing::replace_passthrough_placeholders},
model::{SectionLevel, substitution::SubstitutionPlan},
};
#[derive(Debug)]
pub(crate) struct PositionWithOffset {
pub(crate) offset: usize,
pub(crate) position: crate::Position,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum MacroAttributeContext {
General,
Image,
}
#[derive(Debug, Default)]
pub(crate) struct BlockParsingMetadata<'input> {
pub(crate) metadata: BlockMetadata<'input>,
pub(crate) title: Title<'input>,
pub(crate) parent_section_level: Option<SectionLevel>,
pub(crate) substitutions: SubstitutionPlan,
pub(crate) hardbreaks: bool,
pub(crate) discrete: bool,
}
pub(crate) const RESERVED_NAMED_ATTRIBUTE_ID: &str = "id";
pub(crate) const RESERVED_NAMED_ATTRIBUTE_ROLE: &str = "role";
pub(crate) const RESERVED_NAMED_ATTRIBUTE_OPTIONS: &str = "opts";
pub(crate) const RESERVED_NAMED_ATTRIBUTE_SUBS: &str = "subs";
pub(crate) fn is_valid_bibliography_id(id: &str) -> bool {
let mut chars = id.chars();
chars
.next()
.is_some_and(|first| first.is_alphabetic() || matches!(first, '_' | ':'))
&& chars.all(|character| {
character.is_alphanumeric() || matches!(character, '_' | '-' | ':' | '.')
})
}
pub(crate) fn strip_url_backslash_escapes(text: &str) -> Cow<'_, str> {
if !text.contains('\\') {
return Cow::Borrowed(text);
}
Cow::Owned(
text.replace("\\...", "...")
.replace("\\->", "->")
.replace("\\<-", "<-")
.replace("\\=>", "=>")
.replace("\\<=", "<=")
.replace("\\--", "--"),
)
}
pub(crate) fn restore_url_path(processed: ProcessedContent<'_>) -> String {
let text = if processed.passthroughs.is_empty() {
processed.text
} else {
replace_passthrough_placeholders(&processed.text, &processed).into()
};
strip_url_backslash_escapes(&text).into_owned()
}
pub(crate) fn parse_comma_separated_values<'a>(
state: &ParserState<'a>,
value: &str,
) -> Vec<&'a str> {
value
.split(',')
.map(str::trim)
.filter(|s| !s.is_empty())
.map(|s| state.intern_str(s))
.collect()
}
pub(crate) fn title_looks_like_description_list(title: &str) -> bool {
let trimmed = title.trim();
for marker in &["::::", ":::", "::", ";;"] {
if let Some(pos) = trimmed.find(marker) &&
pos > 0 &&
let Some(after) = trimmed.get(pos + marker.len()..)
&& (after.is_empty() || after.starts_with(' ') || after.starts_with('\t'))
{
return true;
}
}
false
}