///|
/// Detached input values. Shared collections are inserted as independent copies.
pub(all) enum In {
Value(Any)
Array(ReadOnlyArray[In])
Map(ReadOnlyArray[(String, In)])
Text(ReadOnlyArray[TextDelta])
XmlElement(String, ReadOnlyArray[(String, In)], ReadOnlyArray[In])
XmlFragment(ReadOnlyArray[In])
XmlText(ReadOnlyArray[TextDelta], ReadOnlyArray[(String, In)])
Subdoc(Doc)
WeakLink(WeakPrelim)
}
///|
/// Input delta for Text::apply_delta. Retain/delete lengths use the document's
/// offset mode; inserted strings become text and other values become embeds.
pub type TextDelta = DeltaOf[In]
///|
pub fn Out::to_input(self : Out) -> In {
fn text(value : Text) -> ReadOnlyArray[TextDelta] {
ReadOnlyArray::from_array(
value
.to_delta()
.map(fn(d) { TextDelta::Inserted(d.insert.to_input(), d.attributes) })[:],
)
}
fn entries(value : MapRef) -> ReadOnlyArray[(String, In)] {
ReadOnlyArray::from_array(
value.entries().map(fn(e) { (e.0, e.1.to_input()) })[:],
)
}
fn xml_attributes(value : MapRef) -> ReadOnlyArray[(String, In)] {
ReadOnlyArray::from_array(
value.entries().map(fn(e) { (e.0, In::Value(String(e.1.to_string()))) }),
)
}
fn children(value : XmlFragment) -> ReadOnlyArray[In] {
ReadOnlyArray::from_array(
value
.children()
.map(fn(n) {
match n {
Element(e) => Out::XmlElement(e).to_input()
Fragment(f) => Out::XmlFragment(f).to_input()
Text(t) => Out::XmlText(t).to_input()
}
})[:],
)
}
match self {
Value(value) => Value(value)
Array(value) =>
Array(ReadOnlyArray::from_array(value.to_array().map(Out::to_input)[:]))
Map(value) => Map(entries(value))
Text(value) => Text(text(value))
XmlElement(value) =>
XmlElement(
value.tag,
xml_attributes(value.as_map()),
children(value.as_fragment()),
)
XmlFragment(value) => XmlFragment(children(value))
XmlText(value) =>
XmlText(text(value.as_text()), xml_attributes(value.as_map()))
Subdoc(value) => Subdoc(value.unloaded_copy())
UndefinedRef(value) => value.infer_input()
WeakLink(value) => WeakLink(value.as_prelim())
}
}
///|
fn In::content(self : In, text : Bool) -> Content {
match self {
Value(String(value)) if text => Characters(value)
Value(value) => if text { Embed(value) } else { Values([value]) }
Array(_) => Shared(SharedArray)
Map(_) => Shared(SharedMap)
Text(_) => Shared(SharedText)
XmlElement(tag, _, _) => Shared(SharedXmlElement(tag))
XmlFragment(_) => Shared(SharedXmlFragment)
XmlText(_, _) => Shared(SharedXmlText)
Subdoc(child) => Subdocument(child)
WeakLink(value) => Shared(SharedWeak(value.source()))
}
}
///|
fn In::populate(self : In, doc : Doc, id : Id) -> Out {
let name : Parent = Nested(id)
fn entries(values : ReadOnlyArray[(String, In)]) -> Unit {
let map : MapRef = { doc, name, }
for (key, value) in values {
map.insert_input(key, value) |> ignore
}
}
fn children(values : ReadOnlyArray[In]) -> Unit {
let array : ArrayRef = { doc, name, }
for index in 0.. ignore
}
}
match self {
Value(value) => Value(value)
Subdoc(child) => Subdoc(child)
WeakLink(value) => WeakLink({ doc, name, link: value.source(), })
Array(values) => {
children(values)
Array({ doc, name, })
}
Map(values) => {
entries(values)
Map({ doc, name, })
}
Text(delta) => {
let text : Text = { doc, name, }
text.apply_delta(delta.iter().to_array())
Text(text)
}
XmlElement(tag, attrs, values) => {
entries(attrs)
children(values)
XmlElement({ doc, name, tag, })
}
XmlFragment(values) => {
children(values)
XmlFragment({ doc, name, })
}
XmlText(delta, attrs) => {
entries(attrs)
let text : Text = { doc, name, }
text.apply_delta(delta.iter().to_array())
XmlText({ doc, name, })
}
}
}
///|
pub fn ArrayRef::insert(self : ArrayRef, index : Int32, input : In) -> Out {
self.doc.with_mutation(fn() {
if input is Subdoc(child) {
self.doc.check_subdocument(child)
}
let sequence : Text = { doc: self.doc, name: self.name, }
let (left, right) = sequence.position(index)
let id = self.doc.insert_content(
self.name,
None,
left,
right,
input.content(false),
)
input.populate(self.doc, id)
})
}
///|
pub fn MapRef::insert_input(self : MapRef, key : String, input : In) -> Out {
self.doc.with_mutation(fn() {
if input is Subdoc(child) {
self.doc.check_subdocument(child)
}
let id = self.doc.insert_content(
self.name,
Some(key),
self.doc.branch(self.name).values.get(key),
None,
input.content(false),
)
input.populate(self.doc, id)
})
}
///|
/// Insert a primitive or shared collection as one embedded text element.
/// Omitted attributes inherit the surrounding formatting; an explicit array
/// replaces it. The returned handle refers to the newly integrated collection.
pub fn Text::insert_embed_input(
self : Text,
index : Int32,
input : In,
attributes? : Array[(String, Any)],
) -> Out {
self.doc.with_mutation(fn() {
if input is Subdoc(child) {
self.doc.check_subdocument(child)
}
let pos = self.rich_position(index)
let content = match input {
Value(value) => Embed(value)
_ => input.content(true)
}
let id = match attributes {
Some(attrs) => pos.insert_formatted(content, attrs)
None => {
pos.insert(content)
pos.left.unwrap()
}
}
input.populate(self.doc, id)
})
}
///|
pub fn Text::apply_delta(self : Text, delta : Array[TextDelta]) -> Unit {
self.doc.with_mutation(fn() {
let pos = self.rich_position(0)
for op in delta {
match op {
Inserted(input, attributes) => {
if input is Subdoc(child) {
self.doc.check_subdocument(child)
}
let content = input.content(true)
if content.length() == 0 {
continue
}
let id = pos.insert_formatted(content, attributes.iter().to_array())
input.populate(self.doc, id) |> ignore
}
Deleted(length) => pos.remove(length, cleanup_empty=true)
Retain(length, attributes) => {
guard length >= 0 else { abort("negative retain length") }
pos.format(length, attributes.iter().to_array())
}
}
}
})
}