use std::fmt;
use serde_json::{json, Value};
use crate::error::DocError;
use crate::fragment::Fragment;
use crate::map::{Mapping, StepMap};
use crate::mark::Mark;
use crate::node::Node;
use crate::schema::Schema;
use crate::slice::Slice;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StepError(pub String);
impl fmt::Display for StepError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "step failed: {}", self.0)
}
}
impl std::error::Error for StepError {}
pub trait Step: fmt::Debug + Send + Sync {
fn apply(&self, doc: &Node, schema: &Schema) -> Result<Node, StepError>;
fn get_map(&self) -> StepMap;
fn invert(&self, doc: &Node) -> Result<Box<dyn Step>, StepError>;
fn map(&self, mapping: &Mapping) -> Option<Box<dyn Step>>;
fn to_json(&self) -> Value;
fn clone_box(&self) -> Box<dyn Step>;
}
impl Clone for Box<dyn Step> {
fn clone(&self) -> Self {
self.clone_box()
}
}
fn slice_to_json(slice: &Slice) -> Value {
if slice.is_empty() {
return json!({});
}
let content: Vec<Value> = slice.content().iter().map(Node::to_json).collect();
json!({
"content": content,
"openStart": slice.open_start(),
"openEnd": slice.open_end(),
})
}
fn slice_from_json(schema: &Schema, v: &Value) -> Result<Slice, DocError> {
let obj = v
.as_object()
.ok_or_else(|| DocError::MalformedJson("slice must be an object".into()))?;
let content = match obj.get("content") {
None => return Ok(Slice::empty()),
Some(Value::Array(a)) => a
.iter()
.map(|n| schema.node_from_json(n))
.collect::<Result<Vec<_>, _>>()?,
Some(_) => {
return Err(DocError::MalformedJson(
"slice.content must be an array".into(),
))
}
};
let open_start = obj.get("openStart").and_then(Value::as_u64).unwrap_or(0) as usize;
let open_end = obj.get("openEnd").and_then(Value::as_u64).unwrap_or(0) as usize;
Ok(Slice::new(
Fragment::from_nodes(content),
open_start,
open_end,
))
}
#[derive(Debug, Clone)]
pub struct ReplaceStep {
from: usize,
to: usize,
slice: Slice,
}
impl ReplaceStep {
pub fn new(from: usize, to: usize, slice: Slice) -> Self {
ReplaceStep { from, to, slice }
}
}
impl Step for ReplaceStep {
fn apply(&self, doc: &Node, schema: &Schema) -> Result<Node, StepError> {
doc.replace(self.from, self.to, &self.slice, schema)
.map_err(|e| StepError(e.to_string()))
}
fn get_map(&self) -> StepMap {
StepMap::new(vec![self.from, self.to - self.from, self.slice.size()])
}
fn invert(&self, doc: &Node) -> Result<Box<dyn Step>, StepError> {
let removed = doc
.slice(self.from, self.to)
.map_err(|e| StepError(e.to_string()))?;
Ok(Box::new(ReplaceStep {
from: self.from,
to: self.from + self.slice.size(),
slice: removed,
}))
}
fn map(&self, mapping: &Mapping) -> Option<Box<dyn Step>> {
let from = mapping.map_result(self.from, 1);
let to = mapping.map_result(self.to, -1);
if from.deleted_across() && to.deleted_across() {
return None;
}
Some(Box::new(ReplaceStep {
from: from.pos,
to: from.pos.max(to.pos),
slice: self.slice.clone(),
}))
}
fn to_json(&self) -> Value {
json!({
"stepType": "replace",
"from": self.from,
"to": self.to,
"slice": slice_to_json(&self.slice),
})
}
fn clone_box(&self) -> Box<dyn Step> {
Box::new(self.clone())
}
}
fn map_inline(frag: &Fragment, f: &dyn Fn(&Node) -> Node) -> Fragment {
let mut out = Vec::new();
for child in frag.iter() {
let mut c = child.clone();
if c.content().child_count() > 0 {
c = c.copy_content(map_inline(c.content(), f));
}
if c.is_inline() {
c = f(&c);
}
out.push(c);
}
Fragment::from_nodes(out)
}
#[derive(Debug, Clone)]
pub struct AddMarkStep {
from: usize,
to: usize,
mark: Mark,
}
impl AddMarkStep {
pub fn new(from: usize, to: usize, mark: Mark) -> Self {
AddMarkStep { from, to, mark }
}
}
impl Step for AddMarkStep {
fn apply(&self, doc: &Node, schema: &Schema) -> Result<Node, StepError> {
let old = doc
.slice(self.from, self.to)
.map_err(|e| StepError(e.to_string()))?;
let mark = self.mark.clone();
let content = map_inline(old.content(), &|n| n.with_marks(mark.add_to_set(n.marks())));
let slice = Slice::new(content, old.open_start(), old.open_end());
doc.replace(self.from, self.to, &slice, schema)
.map_err(|e| StepError(e.to_string()))
}
fn get_map(&self) -> StepMap {
StepMap::identity()
}
fn invert(&self, _doc: &Node) -> Result<Box<dyn Step>, StepError> {
Ok(Box::new(RemoveMarkStep {
from: self.from,
to: self.to,
mark: self.mark.clone(),
}))
}
fn map(&self, mapping: &Mapping) -> Option<Box<dyn Step>> {
let from = mapping.map_result(self.from, 1);
let to = mapping.map_result(self.to, -1);
if (from.deleted() && to.deleted()) || from.pos >= to.pos {
return None;
}
Some(Box::new(AddMarkStep {
from: from.pos,
to: from.pos.max(to.pos),
mark: self.mark.clone(),
}))
}
fn to_json(&self) -> Value {
json!({
"stepType": "addMark",
"mark": self.mark.to_json(),
"from": self.from,
"to": self.to,
})
}
fn clone_box(&self) -> Box<dyn Step> {
Box::new(self.clone())
}
}
#[derive(Debug, Clone)]
pub struct RemoveMarkStep {
from: usize,
to: usize,
mark: Mark,
}
impl RemoveMarkStep {
pub fn new(from: usize, to: usize, mark: Mark) -> Self {
RemoveMarkStep { from, to, mark }
}
}
impl Step for RemoveMarkStep {
fn apply(&self, doc: &Node, schema: &Schema) -> Result<Node, StepError> {
let old = doc
.slice(self.from, self.to)
.map_err(|e| StepError(e.to_string()))?;
let mark = self.mark.clone();
let content = map_inline(old.content(), &|n| {
n.with_marks(mark.remove_from_set(n.marks()))
});
let slice = Slice::new(content, old.open_start(), old.open_end());
doc.replace(self.from, self.to, &slice, schema)
.map_err(|e| StepError(e.to_string()))
}
fn get_map(&self) -> StepMap {
StepMap::identity()
}
fn invert(&self, _doc: &Node) -> Result<Box<dyn Step>, StepError> {
Ok(Box::new(AddMarkStep {
from: self.from,
to: self.to,
mark: self.mark.clone(),
}))
}
fn map(&self, mapping: &Mapping) -> Option<Box<dyn Step>> {
let from = mapping.map_result(self.from, 1);
let to = mapping.map_result(self.to, -1);
if (from.deleted() && to.deleted()) || from.pos >= to.pos {
return None;
}
Some(Box::new(RemoveMarkStep {
from: from.pos,
to: from.pos.max(to.pos),
mark: self.mark.clone(),
}))
}
fn to_json(&self) -> Value {
json!({
"stepType": "removeMark",
"mark": self.mark.to_json(),
"from": self.from,
"to": self.to,
})
}
fn clone_box(&self) -> Box<dyn Step> {
Box::new(self.clone())
}
}
fn rebuild_at(node: &Node, pos: usize, f: &dyn Fn(&Node) -> Node) -> Option<Node> {
let (index, offset) = node.content().find_index(pos);
let child = node.content().children().get(index)?;
if offset == pos {
let new_child = f(child);
return Some(node.copy_content(node.content().replace_child(index, new_child)));
}
let inner = rebuild_at(child, pos - offset - 1, f)?;
Some(node.copy_content(node.content().replace_child(index, inner)))
}
#[derive(Debug, Clone)]
pub struct AttrStep {
pos: usize,
attr: String,
value: Value,
}
impl AttrStep {
pub fn new(pos: usize, attr: &str, value: Value) -> Self {
AttrStep {
pos,
attr: attr.to_string(),
value,
}
}
}
impl Step for AttrStep {
fn apply(&self, doc: &Node, _schema: &Schema) -> Result<Node, StepError> {
let attr = self.attr.clone();
let value = self.value.clone();
rebuild_at(doc, self.pos, &|n| {
let mut attrs = n.attrs().clone();
attrs.insert(attr.clone(), value.clone());
n.with_attrs(attrs)
})
.ok_or_else(|| StepError("no node at the attribute step's position".into()))
}
fn get_map(&self) -> StepMap {
StepMap::identity()
}
fn invert(&self, doc: &Node) -> Result<Box<dyn Step>, StepError> {
let node = doc
.node_at(self.pos)
.ok_or_else(|| StepError("no node at the attribute step's position".into()))?;
let old = node.attrs().get(&self.attr).cloned().unwrap_or(Value::Null);
Ok(Box::new(AttrStep {
pos: self.pos,
attr: self.attr.clone(),
value: old,
}))
}
fn map(&self, mapping: &Mapping) -> Option<Box<dyn Step>> {
let r = mapping.map_result(self.pos, 1);
if r.deleted_after() {
None
} else {
Some(Box::new(AttrStep {
pos: r.pos,
attr: self.attr.clone(),
value: self.value.clone(),
}))
}
}
fn to_json(&self) -> Value {
json!({
"stepType": "attr",
"pos": self.pos,
"attr": self.attr,
"value": self.value,
})
}
fn clone_box(&self) -> Box<dyn Step> {
Box::new(self.clone())
}
}
#[derive(Debug, Clone)]
pub struct ReplaceAroundStep {
from: usize,
to: usize,
gap_from: usize,
gap_to: usize,
slice: Slice,
insert: usize,
}
impl ReplaceAroundStep {
pub fn new(
from: usize,
to: usize,
gap_from: usize,
gap_to: usize,
slice: Slice,
insert: usize,
) -> Self {
ReplaceAroundStep {
from,
to,
gap_from,
gap_to,
slice,
insert,
}
}
}
impl Step for ReplaceAroundStep {
fn apply(&self, doc: &Node, schema: &Schema) -> Result<Node, StepError> {
let gap = doc
.slice(self.gap_from, self.gap_to)
.map_err(|e| StepError(e.to_string()))?;
if gap.open_start() > 0 || gap.open_end() > 0 {
return Err(StepError("structure gap can't be inserted".into()));
}
let inserted = self
.slice
.insert_at(self.insert, gap.content().clone())
.ok_or_else(|| StepError("content does not fit in gap".into()))?;
doc.replace(self.from, self.to, &inserted, schema)
.map_err(|e| StepError(e.to_string()))
}
fn get_map(&self) -> StepMap {
StepMap::new(vec![
self.from,
self.gap_from - self.from,
self.insert,
self.gap_to,
self.to - self.gap_to,
self.slice.size().saturating_sub(self.insert),
])
}
fn invert(&self, doc: &Node) -> Result<Box<dyn Step>, StepError> {
let gap = self.gap_to - self.gap_from;
let removed = doc
.slice(self.from, self.to)
.map_err(|e| StepError(e.to_string()))?
.remove_between(self.gap_from - self.from, self.gap_to - self.from)
.ok_or_else(|| StepError("cannot invert replace-around".into()))?;
Ok(Box::new(ReplaceAroundStep {
from: self.from,
to: self.from + self.slice.size() + gap,
gap_from: self.from + self.insert,
gap_to: self.from + self.insert + gap,
slice: removed,
insert: self.gap_from - self.from,
}))
}
fn map(&self, mapping: &Mapping) -> Option<Box<dyn Step>> {
let from = mapping.map_result(self.from, 1);
let to = mapping.map_result(self.to, -1);
let gap_from = if self.from == self.gap_from {
from.pos
} else {
mapping.map(self.gap_from, -1)
};
let gap_to = if self.to == self.gap_to {
to.pos
} else {
mapping.map(self.gap_to, 1)
};
if (from.deleted_across() && to.deleted_across()) || gap_from < from.pos || gap_to > to.pos
{
return None;
}
Some(Box::new(ReplaceAroundStep {
from: from.pos,
to: to.pos,
gap_from,
gap_to,
slice: self.slice.clone(),
insert: self.insert,
}))
}
fn to_json(&self) -> Value {
json!({
"stepType": "replaceAround",
"from": self.from,
"to": self.to,
"gapFrom": self.gap_from,
"gapTo": self.gap_to,
"insert": self.insert,
"slice": slice_to_json(&self.slice),
})
}
fn clone_box(&self) -> Box<dyn Step> {
Box::new(self.clone())
}
}
pub fn step_from_json(schema: &Schema, v: &Value) -> Result<Box<dyn Step>, DocError> {
let obj = v
.as_object()
.ok_or_else(|| DocError::MalformedJson("step must be an object".into()))?;
let kind = obj
.get("stepType")
.and_then(Value::as_str)
.ok_or_else(|| DocError::MalformedJson("step missing `stepType`".into()))?;
match kind {
"replace" => {
let from = obj
.get("from")
.and_then(Value::as_u64)
.ok_or_else(|| DocError::MalformedJson("replace step missing `from`".into()))?
as usize;
let to = obj
.get("to")
.and_then(Value::as_u64)
.ok_or_else(|| DocError::MalformedJson("replace step missing `to`".into()))?
as usize;
let slice = match obj.get("slice") {
Some(s) => slice_from_json(schema, s)?,
None => Slice::empty(),
};
Ok(Box::new(ReplaceStep::new(from, to, slice)))
}
"addMark" | "removeMark" => {
let from = obj
.get("from")
.and_then(Value::as_u64)
.ok_or_else(|| DocError::MalformedJson("mark step missing `from`".into()))?
as usize;
let to = obj
.get("to")
.and_then(Value::as_u64)
.ok_or_else(|| DocError::MalformedJson("mark step missing `to`".into()))?
as usize;
let mark = schema.mark_from_json(
obj.get("mark")
.ok_or_else(|| DocError::MalformedJson("mark step missing `mark`".into()))?,
)?;
Ok(if kind == "addMark" {
Box::new(AddMarkStep::new(from, to, mark))
} else {
Box::new(RemoveMarkStep::new(from, to, mark))
})
}
"replaceAround" => {
let g = |k: &str| {
obj.get(k)
.and_then(Value::as_u64)
.map(|n| n as usize)
.ok_or_else(|| {
DocError::MalformedJson(format!("replaceAround step missing `{k}`"))
})
};
let slice = match obj.get("slice") {
Some(s) => slice_from_json(schema, s)?,
None => Slice::empty(),
};
Ok(Box::new(ReplaceAroundStep::new(
g("from")?,
g("to")?,
g("gapFrom")?,
g("gapTo")?,
slice,
g("insert")?,
)))
}
"attr" => {
let pos = obj
.get("pos")
.and_then(Value::as_u64)
.ok_or_else(|| DocError::MalformedJson("attr step missing `pos`".into()))?
as usize;
let attr = obj
.get("attr")
.and_then(Value::as_str)
.ok_or_else(|| DocError::MalformedJson("attr step missing `attr`".into()))?;
let value = obj.get("value").cloned().unwrap_or(Value::Null);
Ok(Box::new(AttrStep::new(pos, attr, value)))
}
other => Err(DocError::MalformedJson(format!(
"unknown stepType `{other}`"
))),
}
}