mod check;
mod emit;
use std::fmt;
use std::io::{Seek, Write};
use std::path::Path;
pub use crate::markup::Component;
use crate::version::BcfVersion;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TargetVersion {
V2_1,
V3_0,
}
impl From<TargetVersion> for BcfVersion {
fn from(v: TargetVersion) -> Self {
match v {
TargetVersion::V2_1 => BcfVersion::V2_1,
TargetVersion::V3_0 => BcfVersion::V3_0,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Document {
pub version: TargetVersion,
pub extensions: Option<Extensions>,
pub topics: Vec<Topic>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Extensions {
pub topic_types: Vec<String>,
pub topic_statuses: Vec<String>,
pub priorities: Vec<String>,
pub topic_labels: Vec<String>,
pub users: Vec<String>,
pub snippet_types: Vec<String>,
pub stages: Vec<String>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Topic {
pub guid: String,
pub title: String,
pub description: Option<String>,
pub topic_type: Option<String>,
pub topic_status: Option<String>,
pub priority: Option<String>,
pub labels: Vec<String>,
pub creation_date: String,
pub creation_author: String,
pub comments: Vec<Comment>,
pub viewpoints: Vec<Viewpoint>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Comment {
pub guid: String,
pub date: String,
pub author: String,
pub comment: String,
pub viewpoint: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Viewpoint {
pub guid: String,
pub selection: Vec<Component>,
pub camera: Option<Camera>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Camera {
pub projection: Projection,
pub view_point: Vector3,
pub direction: Vector3,
pub up_vector: Vector3,
pub aspect_ratio: Option<f64>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Projection {
Perspective {
field_of_view: f64,
},
Orthogonal {
view_to_world_scale: f64,
},
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct Vector3 {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl Vector3 {
#[must_use]
pub const fn new(x: f64, y: f64, z: f64) -> Self {
Self { x, y, z }
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum WriteError {
Invalid {
at: String,
problem: Invalid,
},
Io(std::io::Error),
Zip {
detail: String,
},
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Invalid {
Guid {
value: String,
},
IfcGuid {
value: String,
},
DateTime {
value: String,
},
Blank,
SurroundingWhitespace {
value: String,
},
ForbiddenCharacter {
ch: char,
},
Missing,
NotInVersion {
version: TargetVersion,
},
NotInExtensions {
value: String,
},
DuplicateGuid {
value: String,
},
UnknownViewpoint {
guid: String,
},
UnidentifiedComponent,
Number {
value: f64,
expected: &'static str,
},
NoTopics,
}
impl fmt::Display for WriteError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
WriteError::Invalid { at, problem } => write!(f, "cannot write BCF: {at}: {problem}"),
WriteError::Io(_) => f.write_str("cannot write BCF archive"),
WriteError::Zip { detail } => write!(f, "cannot encode BCF archive: {detail}"),
}
}
}
impl fmt::Display for Invalid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Invalid::Guid { value } => write!(f, "{value:?} is not a valid GUID"),
Invalid::IfcGuid { value } => write!(f, "{value:?} is not a valid IFC GlobalId"),
Invalid::DateTime { value } => write!(f, "{value:?} is not an xs:dateTime"),
Invalid::Blank => f.write_str("value is blank"),
Invalid::SurroundingWhitespace { value } => {
write!(f, "{value:?} has leading or trailing whitespace")
}
Invalid::ForbiddenCharacter { ch } => {
write!(f, "character {ch:?} cannot be represented in XML 1.0")
}
Invalid::Missing => f.write_str("required by the target version but absent"),
Invalid::NotInVersion { version } => write!(
f,
"not representable in BCF {}",
BcfVersion::from(*version).version_id()
),
Invalid::NotInExtensions { value } => {
write!(f, "{value:?} is not listed in the supplied extensions")
}
Invalid::DuplicateGuid { value } => write!(f, "GUID {value:?} is used more than once"),
Invalid::UnknownViewpoint { guid } => {
write!(f, "no viewpoint {guid:?} in this topic")
}
Invalid::UnidentifiedComponent => {
f.write_str("component has neither IfcGuid nor AuthoringToolId")
}
Invalid::Number { value, expected } => write!(f, "{value} is not {expected}"),
Invalid::NoTopics => f.write_str("a BCF archive needs at least one topic"),
}
}
}
impl std::error::Error for WriteError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
WriteError::Io(source) => Some(source),
_ => None,
}
}
}
impl From<std::io::Error> for WriteError {
fn from(source: std::io::Error) -> Self {
WriteError::Io(source)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct Options {
pub compression: Compression,
}
impl Options {
#[must_use]
pub const fn compression(mut self, compression: Compression) -> Self {
self.compression = compression;
self
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum Compression {
#[default]
Stored,
Deflated {
level: u8,
},
}
pub fn to_vec(doc: &Document) -> Result<Vec<u8>, WriteError> {
to_vec_with(doc, Options::default())
}
pub fn to_vec_with(doc: &Document, options: Options) -> Result<Vec<u8>, WriteError> {
let cursor = to_writer_with(doc, std::io::Cursor::new(Vec::new()), options)?;
Ok(cursor.into_inner())
}
pub fn to_writer<W: Write + Seek>(doc: &Document, sink: W) -> Result<W, WriteError> {
to_writer_with(doc, sink, Options::default())
}
pub fn to_writer_with<W: Write + Seek>(
doc: &Document,
sink: W,
options: Options,
) -> Result<W, WriteError> {
emit::check_options(options)?;
let entries = emit::entries(doc)?;
emit::zip(&entries, sink, options)
}
pub fn to_path(doc: &Document, path: impl AsRef<Path>) -> Result<(), WriteError> {
to_path_with(doc, path, Options::default())
}
pub fn to_path_with(
doc: &Document,
path: impl AsRef<Path>,
options: Options,
) -> Result<(), WriteError> {
emit::check_options(options)?;
let entries = emit::entries(doc)?;
let file = std::fs::File::create(path)?;
let mut file = emit::zip(&entries, std::io::BufWriter::new(file), options)?;
file.flush()?;
Ok(())
}