use alloc::string::String;
use alloc::vec::Vec;
use deser_core::ser::EventSink;
use deser_core::ser::SerializeRef;
use deser_core::{Error, ErrorKind, Event, State};
use crate::format::Format;
use crate::ser::{Node, convert_atom, key_to_string, unsupported_key};
use crate::{write_ascii, write_xml};
enum Open {
Array,
Dict(Option<String>),
}
enum Item {
Null,
Node(Node),
Start(bool),
}
pub(crate) struct TextWriter {
xml: bool,
pub(crate) out: String,
stack: Vec<Open>,
pending: Option<bool>,
done: bool,
pub(crate) limit: usize,
}
impl EventSink for TextWriter {
fn event(
&mut self,
event: Event<'_>,
_value: SerializeRef<'_>,
_state: &mut State,
) -> Result<(), Error> {
TextWriter::event(self, event)
}
fn pause(&mut self) -> bool {
self.out.len() >= self.limit
}
}
impl TextWriter {
pub(crate) fn new(format: Format, mut out: String) -> TextWriter {
let xml = match format {
Format::Xml => true,
Format::Ascii => false,
Format::Binary => unreachable!("binary property lists are not text"),
};
if xml {
out.push_str(write_xml::HEADER);
}
TextWriter {
xml,
out,
stack: Vec::new(),
pending: None,
done: false,
limit: usize::MAX,
}
}
pub(crate) fn finish(&mut self) -> Result<(), Error> {
if !self.done || !self.stack.is_empty() || self.pending.is_some() {
return Err(Error::new(ErrorKind::InvalidState, "incomplete value"));
}
if self.xml {
self.out.push_str(write_xml::FOOTER);
}
Ok(())
}
fn event(&mut self, event: Event<'_>) -> Result<(), Error> {
if let Some(is_map) = self.pending.take() {
match event {
Event::MapEnd if is_map => return self.write_empty(true),
Event::SeqEnd if !is_map => return self.write_empty(false),
_ => self.open(is_map),
}
}
match self.stack.last_mut() {
None => {
if self.done {
return Err(Error::new(ErrorKind::InvalidState, "unexpected event"));
}
match item(event)? {
Item::Null => Err(Error::new(
ErrorKind::UnsupportedType,
"property lists cannot hold null values",
)),
item => self.write_item(item),
}
}
Some(Open::Dict(key @ None)) => match event {
Event::Atom(atom) => {
*key = Some(key_to_string(atom)?);
Ok(())
}
Event::MapEnd => self.close(),
_ => Err(unsupported_key()),
},
Some(Open::Dict(key @ Some(_))) => {
let key = key.take().unwrap();
match item(event)? {
Item::Null => Ok(()),
item => {
self.write_key(&key);
self.write_item(item)
}
}
}
Some(Open::Array) => {
if event == Event::SeqEnd {
return self.close();
}
match item(event)? {
Item::Null => Err(Error::new(
ErrorKind::UnsupportedType,
"property lists cannot hold null values in arrays",
)),
item => self.write_item(item),
}
}
}
}
fn write_key(&mut self, key: &str) {
let depth = self.stack.len();
indent(&mut self.out, depth);
if self.xml {
self.out.push_str("<key>");
write_xml::escape(&mut self.out, key);
self.out.push_str("</key>\n");
} else {
write_ascii::write_str(&mut self.out, key);
self.out.push_str(" = ");
}
}
fn write_item(&mut self, item: Item) -> Result<(), Error> {
let depth = self.stack.len();
if self.xml || matches!(self.stack.last(), Some(Open::Array)) {
indent(&mut self.out, depth);
}
match item {
Item::Node(node) => {
if self.xml {
write_xml::scalar(&mut self.out, &node, depth)?;
} else {
write_ascii::scalar(&mut self.out, &node);
}
self.complete();
}
Item::Start(is_map) => self.pending = Some(is_map),
Item::Null => unreachable!("nulls are handled by the caller"),
}
Ok(())
}
fn open(&mut self, is_map: bool) {
self.out.push_str(match (self.xml, is_map) {
(true, true) => "<dict>\n",
(true, false) => "<array>\n",
(false, true) => "{\n",
(false, false) => "(\n",
});
self.stack.push(if is_map {
Open::Dict(None)
} else {
Open::Array
});
}
fn write_empty(&mut self, is_map: bool) -> Result<(), Error> {
self.out.push_str(match (self.xml, is_map) {
(true, true) => "<dict/>\n",
(true, false) => "<array/>\n",
(false, true) => "{}",
(false, false) => "()",
});
self.complete();
Ok(())
}
fn close(&mut self) -> Result<(), Error> {
let is_map = match self.stack.pop() {
Some(Open::Array) => false,
Some(Open::Dict(None)) => true,
Some(Open::Dict(Some(_))) => {
return Err(Error::new(ErrorKind::InvalidState, "map without value"));
}
None => return Err(Error::new(ErrorKind::InvalidState, "unexpected end")),
};
indent(&mut self.out, self.stack.len());
self.out.push_str(match (self.xml, is_map) {
(true, true) => "</dict>\n",
(true, false) => "</array>\n",
(false, true) => "}",
(false, false) => ")",
});
self.complete();
Ok(())
}
fn complete(&mut self) {
if !self.xml {
self.out.push_str(match self.stack.last() {
Some(Open::Dict(_)) => ";\n",
Some(Open::Array) => ",\n",
None => "\n",
});
}
if self.stack.is_empty() {
self.done = true;
}
}
}
fn item(event: Event<'_>) -> Result<Item, Error> {
Ok(match event {
Event::Atom(atom) => match convert_atom(atom)? {
Some(node) => Item::Node(node),
None => Item::Null,
},
Event::MapStart(_) => Item::Start(true),
Event::SeqStart(_) => Item::Start(false),
Event::MapEnd | Event::SeqEnd => {
return Err(Error::new(ErrorKind::InvalidState, "unexpected end event"));
}
})
}
fn indent(out: &mut String, depth: usize) {
for _ in 0..depth {
out.push('\t');
}
}