///|
pub struct XmlFragment {
priv doc : Doc
priv name : Parent
}
///|
pub struct XmlElement {
priv doc : Doc
priv name : Parent
priv tag : String
}
///|
pub struct XmlText {
priv doc : Doc
priv name : Parent
}
///|
pub(all) enum XmlNode {
Element(XmlElement)
Fragment(XmlFragment)
Text(XmlText)
} derive(Eq)
///|
fn Out::as_xml(self : Out) -> XmlNode? {
match self {
XmlElement(value) => Some(Element(value))
XmlFragment(value) => Some(Fragment(value))
XmlText(value) => Some(Text(value))
_ => None
}
}
///|
pub fn Doc::get_or_insert_xml_fragment(
self : Doc,
name : String,
) -> XmlFragment {
let name : Parent = Root(name)
self.register_kind(name, SharedXmlFragment)
{ doc: self, name, }
}
///|
pub fn XmlElement::tag(self : XmlElement) -> String {
self.tag
}
///|
pub fn XmlElement::as_fragment(self : XmlElement) -> XmlFragment {
{ doc: self.doc, name: self.name, }
}
///|
pub fn XmlText::as_text(self : XmlText) -> Text {
{ doc: self.doc, name: self.name, }
}
///|
pub fn XmlFragment::children(self : XmlFragment) -> Array[XmlNode] {
let sequence : ArrayRef = { doc: self.doc, name: self.name, }
sequence.to_array().filter_map(Out::as_xml)
}
///|
pub fn XmlFragment::length(self : XmlFragment) -> Int32 {
self.children().length()
}
///|
pub fn XmlFragment::remove_range(
self : XmlFragment,
index : Int32,
length : Int32,
) -> Unit {
let sequence : ArrayRef = { doc: self.doc, name: self.name, }
sequence.remove_range(index, length)
}
///|
pub fn XmlFragment::insert_element(
self : XmlFragment,
index : Int32,
tag : String,
) -> XmlElement {
{
doc: self.doc,
name: Nested(self.insert_shared(index, SharedXmlElement(tag))),
tag,
}
}
///|
pub fn XmlFragment::insert_text(self : XmlFragment, index : Int32) -> XmlText {
{ doc: self.doc, name: Nested(self.insert_shared(index, SharedXmlText)), }
}
///|
pub fn XmlFragment::insert_fragment(
self : XmlFragment,
index : Int32,
) -> XmlFragment {
{ doc: self.doc, name: Nested(self.insert_shared(index, SharedXmlFragment)), }
}
///|
pub fn XmlNode::get_string(self : XmlNode) -> String {
match self {
Element(value) => value.get_string()
Fragment(value) => value.get_string()
Text(value) => value.get_string()
}
}
///|
pub fn XmlFragment::get_string(self : XmlFragment) -> String {
let out = StringBuilder()
for child in self.children() {
out.write_string(child.get_string())
}
out.to_string()
}
///|
/// Match upstream serialization: content and attribute values are not escaped.
pub fn XmlElement::get_string(self : XmlElement) -> String {
let out = StringBuilder()
out.write_string("<" + self.tag)
for (key, value) in self.attributes() {
out.write_string(" " + key + "=\"" + value.to_string() + "\"")
}
out.write_string(">")
out.write_string(self.as_fragment().get_string())
out.write_string("" + self.tag + ">")
out.to_string()
}
///|
pub fn XmlText::get_string(self : XmlText) -> String {
xml_text_chunks(self.as_text().to_delta())
}
///|
fn xml_text_chunks(chunks : Array[TextChunk]) -> String {
let out = StringBuilder()
for chunk in chunks {
for (tag, attrs) in chunk.attributes {
out.write_string("<" + tag)
if attrs is Object(fields) {
let fields = fields.iter().to_array()
fields.sort_by(fn(a, b) { a.0.compare(b.0) })
for (key, value) in fields {
out.write_string(" " + key + "=\"" + value.to_string() + "\"")
}
}
out.write_string(">")
}
if chunk.insert is Value(value) {
out.write_string(value.to_string())
}
for (tag, _) in chunk.attributes.iter().to_array().rev() {
out.write_string("" + tag + ">")
}
}
out.to_string()
}
///|
pub fn Out::to_string(self : Out) -> String {
match self {
Value(value) => value.to_string()
UndefinedRef(_) => ""
WeakLink(value) => value.get_string()
Subdoc(value) => "Doc(id: \{value.client_id()}, guid: \{value.guid()})"
Text(value) => value.get_string()
XmlElement(value) => value.get_string()
XmlFragment(value) => value.get_string()
XmlText(value) => value.get_string()
Array(_) | Map(_) => self.to_any().to_string()
}
}
///|
pub fn Any::to_string(self : Any) -> String {
match self {
Undefined => "undefined"
Null => "null"
Bool(value) => value.to_string()
Integer(value) => value.to_string()
Float(value) => value.to_string()
String(value) => value
Array(values) =>
"[" + values.iter().to_array().map(Any::to_string).join(", ") + "]"
Object(fields) =>
"{" +
fields
.iter()
.to_array()
.map(fn(pair) { pair.0 + ": " + pair.1.to_string() })
.join(", ") +
"}"
Buffer(bytes) => {
let out = StringBuilder()
out.write_string("0x")
let digits = "0123456789abcdef"
for byte in bytes {
out.write_char(digits[byte.to_int() / 16].to_int().unsafe_to_char())
out.write_char(digits[byte.to_int() % 16].to_int().unsafe_to_char())
}
out.to_string()
}
}
}
///|
fn Doc::xml_parent(self : Doc, name : Parent) -> XmlNode? {
guard name is Nested(id) &&
self.items.get(id) is Some(item) &&
item.parent is Some(parent) else {
return None
}
match parent {
Root(_) => Some(Fragment({ doc: self, name: parent, }))
Nested(id) =>
match self.items.get(id) {
Some({ content: Shared(kind), .. }) =>
self.shared_output(id, kind).as_xml()
_ => None
}
}
}
///|
pub fn XmlNode::parent(self : XmlNode) -> XmlNode? {
match self {
Element(value) => value.parent()
Fragment(value) => value.parent()
Text(value) => value.parent()
}
}
///|
pub fn XmlElement::as_map(self : XmlElement) -> MapRef {
{ doc: self.doc, name: self.name, }
}
///|
pub fn XmlElement::insert_attribute(
self : XmlElement,
key : String,
value : Any,
) -> Unit {
self.as_map().insert(key, value)
}
///|
pub fn XmlElement::remove_attribute(self : XmlElement, key : String) -> Unit {
self.as_map().remove(key)
}
///|
pub fn XmlElement::get_attribute(self : XmlElement, key : String) -> Out? {
self.as_map().get(key)
}
///|
pub fn XmlElement::attributes(self : XmlElement) -> Array[(String, Out)] {
self.as_map().entries()
}
///|
pub fn XmlElement::parent(self : XmlElement) -> XmlNode? {
self.doc.xml_parent(self.name)
}
///|
pub fn XmlFragment::as_map(self : XmlFragment) -> MapRef {
{ doc: self.doc, name: self.name, }
}
///|
pub fn XmlFragment::insert_attribute(
self : XmlFragment,
key : String,
value : Any,
) -> Unit {
self.as_map().insert(key, value)
}
///|
pub fn XmlFragment::remove_attribute(self : XmlFragment, key : String) -> Unit {
self.as_map().remove(key)
}
///|
pub fn XmlFragment::get_attribute(self : XmlFragment, key : String) -> Out? {
self.as_map().get(key)
}
///|
pub fn XmlFragment::attributes(self : XmlFragment) -> Array[(String, Out)] {
self.as_map().entries()
}
///|
pub fn XmlFragment::parent(self : XmlFragment) -> XmlNode? {
self.doc.xml_parent(self.name)
}
///|
pub fn XmlText::as_map(self : XmlText) -> MapRef {
{ doc: self.doc, name: self.name, }
}
///|
pub fn XmlText::insert_attribute(
self : XmlText,
key : String,
value : Any,
) -> Unit {
self.as_map().insert(key, value)
}
///|
pub fn XmlText::remove_attribute(self : XmlText, key : String) -> Unit {
self.as_map().remove(key)
}
///|
pub fn XmlText::get_attribute(self : XmlText, key : String) -> Out? {
self.as_map().get(key)
}
///|
pub fn XmlText::attributes(self : XmlText) -> Array[(String, Out)] {
self.as_map().entries()
}
///|
pub fn XmlText::parent(self : XmlText) -> XmlNode? {
self.doc.xml_parent(self.name)
}
///|
// Yrs XML uses Branch::index_to_ptr, which stops before deleted entries
// following the selected visible element. Arrays skip these entries instead.
fn XmlFragment::insert_shared(
self : XmlFragment,
index : Int32,
kind : SharedKind,
) -> Id {
guard index >= 0 && index <= self.length() else {
abort("XML index out of bounds")
}
let mut remaining = index
let mut left = None
let mut right = self.doc.branch(self.name).head
while remaining > 0 && right is Some(id) {
let item = self.doc.items[id]
if !item.deleted {
remaining -= item.content.visible_length()
}
left = right
right = item.right
}
self.doc.insert_content(self.name, None, left, right, Shared(kind))
}