Custom Formats
Implement the Format trait to create custom import/export formats.
The Format Trait
use spatial_narrative::io::Format;
use spatial_narrative::core::Narrative;
use spatial_narrative::error::Result;
use std::io::{Read, Write};
pub trait Format {
/// Import a narrative from a reader
fn import<R: Read>(&self, reader: &mut R) -> Result<Narrative>;
/// Export a narrative to a writer
fn export<W: Write>(&self, narrative: &Narrative, writer: &mut W) -> Result<()>;
/// Import from a string (has default implementation)
fn import_str(&self, s: &str) -> Result<Narrative> {
self.import(&mut s.as_bytes())
}
/// Export to a string (has default implementation)
fn export_str(&self, narrative: &Narrative) -> Result<String> {
let mut buf = Vec::new();
self.export(narrative, &mut buf)?;
Ok(String::from_utf8(buf)?)
}
}
Example: XML Format
use spatial_narrative::io::Format;
use spatial_narrative::core::{Narrative, NarrativeBuilder, Event, Location, Timestamp};
use spatial_narrative::error::{Error, Result};
use std::io::{Read, Write};
pub struct XmlFormat;
impl XmlFormat {
pub fn new() -> Self {
Self
}
}
impl Format for XmlFormat {
fn import<R: Read>(&self, reader: &mut R) -> Result<Narrative> {
let mut content = String::new();
reader.read_to_string(&mut content)?;
// Parse XML (using quick-xml or similar)
// This is a simplified example
let mut builder = NarrativeBuilder::new();
// ... parse events from XML ...
Ok(builder.build())
}
fn export<W: Write>(&self, narrative: &Narrative, writer: &mut W) -> Result<()> {
writeln!(writer, r#"<?xml version="1.0" encoding="UTF-8"?>"#)?;
writeln!(writer, "<narrative>")?;
if let Some(title) = &narrative.title {
writeln!(writer, " <title>{}</title>", escape_xml(title))?;
}
writeln!(writer, " <events>")?;
for event in &narrative.events {
writeln!(writer, " <event>")?;
writeln!(writer, " <lat>{}</lat>", event.location.lat)?;
writeln!(writer, " <lon>{}</lon>", event.location.lon)?;
writeln!(writer, " <timestamp>{}</timestamp>",
event.timestamp.to_rfc3339())?;
writeln!(writer, " <text>{}</text>", escape_xml(&event.text))?;
writeln!(writer, " </event>")?;
}
writeln!(writer, " </events>")?;
writeln!(writer, "</narrative>")?;
Ok(())
}
}
fn escape_xml(s: &str) -> String {
s.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
}
Example: KML Format
For Google Earth compatibility:
pub struct KmlFormat;
impl Format for KmlFormat {
fn export<W: Write>(&self, narrative: &Narrative, writer: &mut W) -> Result<()> {
writeln!(writer, r#"<?xml version="1.0" encoding="UTF-8"?>"#)?;
writeln!(writer, r#"<kml xmlns="http://www.opengis.net/kml/2.2">"#)?;
writeln!(writer, "<Document>")?;
if let Some(title) = &narrative.title {
writeln!(writer, " <name>{}</name>", title)?;
}
for event in &narrative.events {
writeln!(writer, " <Placemark>")?;
writeln!(writer, " <name>{}</name>", escape_xml(&event.text))?;
writeln!(writer, " <TimeStamp>")?;
writeln!(writer, " <when>{}</when>", event.timestamp.to_rfc3339())?;
writeln!(writer, " </TimeStamp>")?;
writeln!(writer, " <Point>")?;
writeln!(writer, " <coordinates>{},{}</coordinates>",
event.location.lon, event.location.lat)?;
writeln!(writer, " </Point>")?;
writeln!(writer, " </Placemark>")?;
}
writeln!(writer, "</Document>")?;
writeln!(writer, "</kml>")?;
Ok(())
}
fn import<R: Read>(&self, _reader: &mut R) -> Result<Narrative> {
// KML import implementation
todo!("KML import not yet implemented")
}
}
Using Custom Formats
let narrative = create_narrative();
// Use custom XML format
let xml_format = XmlFormat::new();
let xml = xml_format.export_str(&narrative)?;
// Use custom KML format
let kml_format = KmlFormat::new();
let kml = kml_format.export_str(&narrative)?;
// Save to file
let mut file = File::create("narrative.kml")?;
kml_format.export(&narrative, &mut file)?;
Best Practices
- Handle errors gracefully: Return
Resultwith descriptive errors - Use buffered I/O: Wrap readers/writers in
BufReader/BufWriter - Support round-trips: Ensure
import(export(n)) == nwhere possible - Document limitations: Note what metadata is lost in conversion
- Validate on import: Check for required fields and valid values
Error Handling
Use the library's error types:
use spatial_narrative::error::{Error, Result};
impl Format for MyFormat {
fn import<R: Read>(&self, reader: &mut R) -> Result<Narrative> {
let mut content = String::new();
reader.read_to_string(&mut content)?;
if content.is_empty() {
return Err(Error::Parse("Empty input".to_string()));
}
// Parse and validate...
Ok(narrative)
}
}