use crate::node::Node;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", default)]
pub struct NormalizeOptions {
pub merge_adjacent_text: bool,
pub remove_empty_text: bool,
pub remove_empty_nodes: bool,
}
impl Default for NormalizeOptions {
fn default() -> Self {
Self {
merge_adjacent_text: true,
remove_empty_text: true,
remove_empty_nodes: false,
}
}
}
impl Node {
pub fn normalize(&mut self) {
self.normalize_with(&NormalizeOptions::default());
}
pub fn normalize_with(&mut self, opts: &NormalizeOptions) {
if let Some(children) = self.content.as_mut() {
for child in children.iter_mut() {
child.normalize_with(opts);
}
normalize_children(children, opts);
}
}
}
#[inline]
fn is_text(n: &Node) -> bool {
n.node_type.as_deref() == Some("text")
}
#[inline]
fn is_empty_text(n: &Node) -> bool {
is_text(n) && n.text.as_deref().unwrap_or("").is_empty()
}
#[inline]
fn mergeable(a: &Node, b: &Node) -> bool {
is_text(a) && is_text(b) && a.marks == b.marks && a.attrs == b.attrs && a.extra == b.extra
}
pub(crate) fn normalize_children(children: &mut Vec<Node>, opts: &NormalizeOptions) {
if opts.remove_empty_text {
children.retain(|c| !is_empty_text(c));
}
if opts.merge_adjacent_text && !children.is_empty() {
let mut w = 0;
for r in 1..children.len() {
if mergeable(&children[w], &children[r]) {
if let Some(t) = children[r].text.take() {
children[w]
.text
.get_or_insert_with(String::new)
.push_str(&t);
}
} else {
w += 1;
if w != r {
children.swap(w, r);
}
}
}
children.truncate(w + 1);
}
if opts.remove_empty_nodes {
children.retain(|c| is_text(c) || !c.content.as_ref().is_some_and(Vec::is_empty));
}
}