use crate::arena_tree::Node;
use std::cell::RefCell;
use std::convert::TryFrom;
#[cfg(feature = "shortcodes")]
pub use crate::parser::shortcodes::NodeShortCode;
pub use crate::parser::alert::{AlertType, NodeAlert};
pub use crate::parser::math::NodeMath;
pub use crate::parser::multiline_block_quote::NodeMultilineBlockQuote;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(test, derive(strum::EnumDiscriminants))]
#[cfg_attr(
test,
strum_discriminants(vis(pub(crate)), derive(strum::VariantArray, Hash))
)]
pub enum NodeValue {
Document,
FrontMatter(String),
BlockQuote,
List(NodeList),
Item(NodeList),
DescriptionList,
DescriptionItem(NodeDescriptionItem),
DescriptionTerm,
DescriptionDetails,
CodeBlock(NodeCodeBlock),
HtmlBlock(NodeHtmlBlock),
Paragraph,
Heading(NodeHeading),
ThematicBreak,
FootnoteDefinition(NodeFootnoteDefinition),
Table(NodeTable),
TableRow(bool),
TableCell,
Text(String),
TaskItem(Option<char>),
SoftBreak,
LineBreak,
Code(NodeCode),
HtmlInline(String),
Raw(String),
Emph,
Strong,
Strikethrough,
Superscript,
Link(NodeLink),
Image(NodeLink),
FootnoteReference(NodeFootnoteReference),
#[cfg(feature = "shortcodes")]
ShortCode(NodeShortCode),
Math(NodeMath),
MultilineBlockQuote(NodeMultilineBlockQuote),
Escaped,
WikiLink(NodeWikiLink),
Underline,
Subscript,
SpoileredText,
EscapedTag(String),
Alert(NodeAlert),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum TableAlignment {
None,
Left,
Center,
Right,
}
impl TableAlignment {
pub(crate) fn xml_name(&self) -> Option<&'static str> {
match *self {
TableAlignment::None => None,
TableAlignment::Left => Some("left"),
TableAlignment::Center => Some("center"),
TableAlignment::Right => Some("right"),
}
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct NodeTable {
pub alignments: Vec<TableAlignment>,
pub num_columns: usize,
pub num_rows: usize,
pub num_nonempty_cells: usize,
}
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct NodeCode {
pub num_backticks: usize,
pub literal: String,
}
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct NodeLink {
pub url: String,
pub title: String,
}
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct NodeWikiLink {
pub url: String,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct NodeList {
pub list_type: ListType,
pub marker_offset: usize,
pub padding: usize,
pub start: usize,
pub delimiter: ListDelimType,
pub bullet_char: u8,
pub tight: bool,
pub is_task_list: bool,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct NodeDescriptionItem {
pub marker_offset: usize,
pub padding: usize,
pub tight: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ListType {
#[default]
Bullet,
Ordered,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ListDelimType {
#[default]
Period,
Paren,
}
impl ListDelimType {
pub(crate) fn xml_name(&self) -> &'static str {
match *self {
ListDelimType::Period => "period",
ListDelimType::Paren => "paren",
}
}
}
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct NodeCodeBlock {
pub fenced: bool,
pub fence_char: u8,
pub fence_length: usize,
pub fence_offset: usize,
pub info: String,
pub literal: String,
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub struct NodeHeading {
pub level: u8,
pub setext: bool,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct NodeHtmlBlock {
pub block_type: u8,
pub literal: String,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct NodeFootnoteDefinition {
pub name: String,
pub total_references: u32,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct NodeFootnoteReference {
pub name: String,
pub ref_num: u32,
pub ix: u32,
}
impl NodeValue {
pub fn block(&self) -> bool {
matches!(
*self,
NodeValue::Document
| NodeValue::BlockQuote
| NodeValue::FootnoteDefinition(_)
| NodeValue::List(..)
| NodeValue::DescriptionList
| NodeValue::DescriptionItem(_)
| NodeValue::DescriptionTerm
| NodeValue::DescriptionDetails
| NodeValue::Item(..)
| NodeValue::CodeBlock(..)
| NodeValue::HtmlBlock(..)
| NodeValue::Paragraph
| NodeValue::Heading(..)
| NodeValue::ThematicBreak
| NodeValue::Table(..)
| NodeValue::TableRow(..)
| NodeValue::TableCell
| NodeValue::TaskItem(..)
| NodeValue::MultilineBlockQuote(_)
| NodeValue::Alert(_)
)
}
pub fn contains_inlines(&self) -> bool {
matches!(
*self,
NodeValue::Paragraph | NodeValue::Heading(..) | NodeValue::TableCell
)
}
pub fn text(&self) -> Option<&String> {
match *self {
NodeValue::Text(ref t) => Some(t),
_ => None,
}
}
pub fn text_mut(&mut self) -> Option<&mut String> {
match *self {
NodeValue::Text(ref mut t) => Some(t),
_ => None,
}
}
pub(crate) fn accepts_lines(&self) -> bool {
matches!(
*self,
NodeValue::Paragraph | NodeValue::Heading(..) | NodeValue::CodeBlock(..)
)
}
pub(crate) fn xml_node_name(&self) -> &'static str {
match *self {
NodeValue::Document => "document",
NodeValue::BlockQuote => "block_quote",
NodeValue::FootnoteDefinition(_) => "footnote_definition",
NodeValue::List(..) => "list",
NodeValue::DescriptionList => "description_list",
NodeValue::DescriptionItem(_) => "description_item",
NodeValue::DescriptionTerm => "description_term",
NodeValue::DescriptionDetails => "description_details",
NodeValue::Item(..) => "item",
NodeValue::CodeBlock(..) => "code_block",
NodeValue::HtmlBlock(..) => "html_block",
NodeValue::Paragraph => "paragraph",
NodeValue::Heading(..) => "heading",
NodeValue::ThematicBreak => "thematic_break",
NodeValue::Table(..) => "table",
NodeValue::TableRow(..) => "table_row",
NodeValue::TableCell => "table_cell",
NodeValue::Text(..) => "text",
NodeValue::SoftBreak => "softbreak",
NodeValue::LineBreak => "linebreak",
NodeValue::Image(..) => "image",
NodeValue::Link(..) => "link",
NodeValue::Emph => "emph",
NodeValue::Strong => "strong",
NodeValue::Code(..) => "code",
NodeValue::HtmlInline(..) => "html_inline",
NodeValue::Raw(..) => "raw",
NodeValue::Strikethrough => "strikethrough",
NodeValue::FrontMatter(_) => "frontmatter",
NodeValue::TaskItem { .. } => "taskitem",
NodeValue::Superscript => "superscript",
NodeValue::FootnoteReference(..) => "footnote_reference",
#[cfg(feature = "shortcodes")]
NodeValue::ShortCode(_) => "shortcode",
NodeValue::MultilineBlockQuote(_) => "multiline_block_quote",
NodeValue::Escaped => "escaped",
NodeValue::Math(..) => "math",
NodeValue::WikiLink(..) => "wikilink",
NodeValue::Underline => "underline",
NodeValue::Subscript => "subscript",
NodeValue::SpoileredText => "spoiler",
NodeValue::EscapedTag(_) => "escaped_tag",
NodeValue::Alert(_) => "alert",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Ast {
pub value: NodeValue,
pub sourcepos: Sourcepos,
pub(crate) internal_offset: usize,
pub(crate) content: String,
pub(crate) open: bool,
pub(crate) last_line_blank: bool,
pub(crate) table_visited: bool,
pub(crate) line_offsets: Vec<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Sourcepos {
pub start: LineColumn,
pub end: LineColumn,
}
impl std::fmt::Display for Sourcepos {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}:{}-{}:{}",
self.start.line, self.start.column, self.end.line, self.end.column,
)
}
}
impl From<(usize, usize, usize, usize)> for Sourcepos {
fn from(sp: (usize, usize, usize, usize)) -> Sourcepos {
Sourcepos {
start: LineColumn {
line: sp.0,
column: sp.1,
},
end: LineColumn {
line: sp.2,
column: sp.3,
},
}
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct LineColumn {
pub line: usize,
pub column: usize,
}
impl From<(usize, usize)> for LineColumn {
fn from(lc: (usize, usize)) -> LineColumn {
LineColumn {
line: lc.0,
column: lc.1,
}
}
}
impl LineColumn {
pub fn column_add(&self, offset: isize) -> LineColumn {
LineColumn {
line: self.line,
column: usize::try_from((self.column as isize) + offset).unwrap(),
}
}
}
impl Ast {
pub fn new(value: NodeValue, start: LineColumn) -> Self {
Ast {
value,
content: String::new(),
sourcepos: (start.line, start.column, start.line, 0).into(),
internal_offset: 0,
open: true,
last_line_blank: false,
table_visited: false,
line_offsets: Vec::with_capacity(0),
}
}
}
pub type AstNode<'a> = Node<'a, RefCell<Ast>>;
impl<'a> From<NodeValue> for AstNode<'a> {
fn from(value: NodeValue) -> Self {
Node::new(RefCell::new(Ast::new(value, LineColumn::default())))
}
}
impl<'a> From<Ast> for AstNode<'a> {
fn from(ast: Ast) -> Self {
Node::new(RefCell::new(ast))
}
}
#[derive(Debug, Clone)]
pub enum ValidationError<'a> {
InvalidChildType {
parent: &'a AstNode<'a>,
child: &'a AstNode<'a>,
},
}
impl<'a> Node<'a, RefCell<Ast>> {
pub fn validate(&'a self) -> Result<(), ValidationError<'a>> {
let mut stack = vec![self];
while let Some(node) = stack.pop() {
if let Some(parent) = node.parent() {
if !can_contain_type(parent, &node.data.borrow().value) {
return Err(ValidationError::InvalidChildType {
parent,
child: node,
});
}
}
stack.extend(node.children());
}
Ok(())
}
}
pub(crate) fn last_child_is_open<'a>(node: &'a AstNode<'a>) -> bool {
node.last_child().map_or(false, |n| n.data.borrow().open)
}
pub fn can_contain_type<'a>(node: &'a AstNode<'a>, child: &NodeValue) -> bool {
match *child {
NodeValue::Document => {
return false;
}
NodeValue::FrontMatter(_) => {
return matches!(node.data.borrow().value, NodeValue::Document);
}
_ => {}
}
match node.data.borrow().value {
NodeValue::Document
| NodeValue::BlockQuote
| NodeValue::FootnoteDefinition(_)
| NodeValue::DescriptionTerm
| NodeValue::DescriptionDetails
| NodeValue::Item(..)
| NodeValue::TaskItem(..) => {
child.block() && !matches!(*child, NodeValue::Item(..) | NodeValue::TaskItem(..))
}
NodeValue::List(..) => matches!(*child, NodeValue::Item(..) | NodeValue::TaskItem(..)),
NodeValue::DescriptionList => matches!(*child, NodeValue::DescriptionItem(_)),
NodeValue::DescriptionItem(_) => matches!(
*child,
NodeValue::DescriptionTerm | NodeValue::DescriptionDetails
),
#[cfg(feature = "shortcodes")]
NodeValue::ShortCode(..) => !child.block(),
NodeValue::Paragraph
| NodeValue::Heading(..)
| NodeValue::Emph
| NodeValue::Strong
| NodeValue::Link(..)
| NodeValue::Image(..)
| NodeValue::WikiLink(..)
| NodeValue::Strikethrough
| NodeValue::Superscript
| NodeValue::SpoileredText
| NodeValue::Underline
| NodeValue::Subscript
| NodeValue::EscapedTag(_)
=> !child.block(),
NodeValue::Table(..) => matches!(*child, NodeValue::TableRow(..)),
NodeValue::TableRow(..) => matches!(*child, NodeValue::TableCell),
#[cfg(not(feature = "shortcodes"))]
NodeValue::TableCell => matches!(
*child,
NodeValue::Text(..)
| NodeValue::Code(..)
| NodeValue::Emph
| NodeValue::Strong
| NodeValue::Link(..)
| NodeValue::Image(..)
| NodeValue::Strikethrough
| NodeValue::HtmlInline(..)
| NodeValue::Math(..)
| NodeValue::WikiLink(..)
| NodeValue::FootnoteReference(..)
| NodeValue::Superscript
| NodeValue::SpoileredText
| NodeValue::Underline
| NodeValue::Subscript
),
#[cfg(feature = "shortcodes")]
NodeValue::TableCell => matches!(
*child,
NodeValue::Text(..)
| NodeValue::Code(..)
| NodeValue::Emph
| NodeValue::Strong
| NodeValue::Link(..)
| NodeValue::Image(..)
| NodeValue::Strikethrough
| NodeValue::HtmlInline(..)
| NodeValue::Math(..)
| NodeValue::WikiLink(..)
| NodeValue::FootnoteReference(..)
| NodeValue::Superscript
| NodeValue::SpoileredText
| NodeValue::Underline
| NodeValue::Subscript
| NodeValue::ShortCode(..)
),
NodeValue::MultilineBlockQuote(_) => {
child.block() && !matches!(*child, NodeValue::Item(..) | NodeValue::TaskItem(..))
}
NodeValue::Alert(_) => {
child.block() && !matches!(*child, NodeValue::Item(..) | NodeValue::TaskItem(..))
}
_ => false,
}
}
pub(crate) fn ends_with_blank_line<'a>(node: &'a AstNode<'a>) -> bool {
let mut it = Some(node);
while let Some(cur) = it {
if cur.data.borrow().last_line_blank {
return true;
}
match cur.data.borrow().value {
NodeValue::List(..) | NodeValue::Item(..) | NodeValue::TaskItem(..) => {
it = cur.last_child()
}
_ => it = None,
};
}
false
}
pub(crate) fn containing_block<'a>(node: &'a AstNode<'a>) -> Option<&'a AstNode<'a>> {
let mut ch = Some(node);
while let Some(n) = ch {
if n.data.borrow().value.block() {
return Some(n);
}
ch = n.parent();
}
None
}