use lazy_static::lazy_static;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::{borrow::Cow, collections::HashMap};
use crate::text::CharacterStyle;
use super::text::{Alignment, Heading, Image, Para};
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum XRefDisplayKind {
#[serde(rename = "document")]
Document,
#[serde(rename = "document+manual")]
DocumentManual,
#[serde(rename = "document+fragment")]
DocumentFragment,
#[serde(rename = "manual")]
Manual,
#[serde(rename = "template")]
Template,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum XRefKind {
None,
Alternate,
Math,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum BlockXRefKind {
None,
Alternate,
Math,
Embed,
Transclude,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct XRef {
#[serde(rename = "@uriid", skip_serializing_if = "Option::is_none")]
pub uriid: Option<String>,
#[serde(rename = "@docid", skip_serializing_if = "Option::is_none")]
pub docid: Option<String>,
#[serde(rename = "@href", skip_serializing_if = "Option::is_none")]
pub href: Option<String>,
#[serde(rename = "$text")]
pub content: String,
#[serde(rename = "@config", skip_serializing_if = "Option::is_none")]
pub config: Option<String>,
#[serde(rename = "@display")]
pub display: XRefDisplayKind,
#[serde(rename = "@frag")]
pub frag_id: String,
#[serde(rename = "@labels", skip_serializing_if = "Option::is_none")]
pub labels: Option<String>,
#[serde(rename = "@level", skip_serializing_if = "Option::is_none")]
pub level: Option<String>,
#[serde(rename = "@reverselink")]
pub reverselink: bool,
#[serde(rename = "@reversetitle", skip_serializing_if = "Option::is_none")]
pub reversetitle: Option<String>,
#[serde(rename = "@title", skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(rename = "@type", skip_serializing_if = "Option::is_none")]
pub xref_type: Option<XRefKind>,
}
impl XRef {
pub fn uriid(uriid: String) -> XRef {
XRef {
uriid: None,
docid: None,
href: Some(uriid),
content: String::new(),
config: None,
display: XRefDisplayKind::Document,
frag_id: "default".to_string(),
labels: None,
level: None,
reverselink: true,
reversetitle: None,
title: None,
xref_type: None,
}
}
pub fn docid(docid: String) -> XRef {
XRef {
uriid: None,
docid: Some(docid),
href: None,
content: String::new(),
config: None,
display: XRefDisplayKind::Document,
frag_id: "default".to_string(),
labels: None,
level: None,
reverselink: true,
reversetitle: None,
title: None,
xref_type: None,
}
}
pub fn href(href: String) -> XRef {
XRef {
uriid: None,
docid: None,
href: Some(href),
content: String::new(),
config: None,
display: XRefDisplayKind::Document,
frag_id: "default".to_string(),
labels: None,
level: None,
reverselink: true,
reversetitle: None,
title: None,
xref_type: None,
}
}
pub fn with_content(self, content: String) -> XRef {
XRef { content, ..self }
}
pub fn with_title(self, title: Option<String>) -> XRef {
XRef { title, ..self }
}
pub fn with_display(self, display: XRefDisplayKind) -> XRef {
XRef { display, ..self }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum PropertyDatatype {
String,
Date,
Datetime,
XRef,
Link,
Markdown,
Markup,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum PropertyValue {
XRef(Box<XRef>),
Link(String),
Markdown(String),
Markup(String),
Value(String),
}
impl From<String> for PropertyValue {
fn from(value: String) -> Self {
Self::Value(value)
}
}
impl PropertyValue {
pub fn datatype(&self) -> PropertyDatatype {
match self {
Self::XRef(_) => PropertyDatatype::XRef,
Self::Link(_) => PropertyDatatype::Link,
Self::Markdown(_) => PropertyDatatype::Markdown,
Self::Markup(_) => PropertyDatatype::Markup,
Self::Value(_) => PropertyDatatype::String,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename = "property")]
pub struct Property {
#[serde(rename = "@name")]
pub name: String,
#[serde(rename = "@title", skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(rename = "@datatype", skip_serializing_if = "Option::is_none")]
pub datatype: Option<PropertyDatatype>,
#[serde(rename = "@multiple", skip_serializing_if = "Option::is_none")]
pub multiple: Option<bool>,
#[serde(rename = "@value", skip_serializing_if = "Option::is_none")]
pub attr_value: Option<String>,
#[serde(rename = "$value", default)]
pub values: Vec<PropertyValue>,
}
lazy_static! {
static ref PROPERTY_BAD_NAME: Regex = Regex::new(r"(^-|[^a-zA-Z0-9_-]+)").unwrap();
}
impl Property {
pub fn with_value(name: String, title: String, value: PropertyValue) -> Self {
let datatype = value.datatype();
Property {
name,
title: Some(title),
values: vec![value],
datatype: Some(datatype),
multiple: None,
attr_value: None,
}
}
pub fn sanitize_name<'a>(name: &'a str, repl: &str) -> Cow<'a, str> {
PROPERTY_BAD_NAME.replace_all(name, repl)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct PropertiesFragment {
#[serde(rename = "@id")]
pub id: String,
#[serde(rename = "@type")]
#[serde(skip_serializing_if = "Option::is_none")]
pub frag_type: Option<String>,
#[serde(rename = "@labels")]
#[serde(skip_serializing_if = "Option::is_none")]
pub labels: Option<String>,
#[serde(rename = "property", default)]
pub properties: Vec<Property>,
#[serde(flatten)]
pub attrs: HashMap<String, String>,
}
impl PropertiesFragment {
pub fn new(id: String) -> PropertiesFragment {
PropertiesFragment {
id,
frag_type: None,
labels: None,
properties: vec![],
attrs: HashMap::new(),
}
}
pub fn with_properties(self, properties: Vec<Property>) -> PropertiesFragment {
PropertiesFragment {
id: self.id,
frag_type: self.frag_type,
labels: self.labels,
properties: vec![self.properties, properties]
.into_iter()
.flatten()
.collect::<Vec<Property>>(),
attrs: self.attrs,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct BlockXRef {
#[serde(rename = "@docid", skip_serializing_if = "Option::is_none")]
pub docid: Option<String>,
#[serde(rename = "@href", skip_serializing_if = "Option::is_none")]
pub href: Option<String>,
#[serde(rename = "@uriid", skip_serializing_if = "Option::is_none")]
pub uriid: Option<String>,
#[serde(rename = "@archived")]
pub archived: Option<bool>,
#[serde(rename = "@config", skip_serializing_if = "Option::is_none")]
pub config: Option<String>,
#[serde(rename = "@display", skip_serializing_if = "Option::is_none")]
pub display: Option<XRefDisplayKind>,
#[serde(rename = "@documenttype", skip_serializing_if = "Option::is_none")]
pub documenttype: Option<String>,
#[serde(rename = "@external", skip_serializing_if = "Option::is_none")]
pub external: Option<bool>,
#[serde(rename = "@frag")]
pub frag: String,
#[serde(rename = "@id", skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(rename = "@labels", skip_serializing_if = "Option::is_none")]
pub labels: Option<String>,
#[serde(rename = "@level", skip_serializing_if = "Option::is_none")]
pub level: Option<u8>,
#[serde(rename = "@mediatype", skip_serializing_if = "Option::is_none")]
pub mediatype: Option<String>,
#[serde(rename = "@reversetitle", skip_serializing_if = "Option::is_none")]
pub reversetitle: Option<String>,
#[serde(rename = "@reverselink", skip_serializing_if = "Option::is_none")]
pub reverselink: Option<bool>,
#[serde(rename = "@reversefrag", skip_serializing_if = "Option::is_none")]
pub reversefrag: Option<String>,
#[serde(rename = "@title", skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(rename = "@type", skip_serializing_if = "Option::is_none")]
pub xref_type: Option<BlockXRefKind>,
#[serde(rename = "@unresolved", skip_serializing_if = "Option::is_none")]
pub unresolved: Option<bool>,
#[serde(rename = "@urititle", skip_serializing_if = "Option::is_none")]
pub urititle: Option<String>,
#[serde(rename = "@urilabels", skip_serializing_if = "Option::is_none")]
pub urilabels: Option<String>,
}
impl BlockXRef {
pub fn docid(docid: String) -> Self {
Self {
docid: Some(docid),
..Default::default()
}
}
pub fn uriid(uriid: String) -> Self {
Self {
uriid: Some(uriid),
..Default::default()
}
}
pub fn href(href: String) -> Self {
Self {
href: Some(href),
..Default::default()
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct XRefFragment {
#[serde(rename = "@id")]
pub id: String,
#[serde(rename = "@type")]
pub frag_type: Option<String>,
#[serde(rename = "@labels")]
pub labels: String,
#[serde(rename = "blockxref", default)]
pub xrefs: Vec<BlockXRef>,
#[serde(flatten)]
pub attrs: HashMap<String, String>,
}
impl XRefFragment {
pub fn new(id: String) -> XRefFragment {
XRefFragment {
id,
frag_type: None,
labels: String::new(),
xrefs: Vec::new(),
attrs: HashMap::new(),
}
}
pub fn with_xrefs(self, xrefs: Vec<BlockXRef>) -> XRefFragment {
XRefFragment {
id: self.id,
frag_type: self.frag_type,
labels: self.labels,
xrefs: vec![self.xrefs, xrefs]
.into_iter()
.flatten()
.collect::<Vec<BlockXRef>>(),
attrs: self.attrs,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct TableCaption {
#[serde(rename = "$text", default)]
caption: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum TablePart {
Header,
Body,
Footer,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default)]
#[serde(rename = "col")]
pub struct TableColumn {
#[serde(rename = "@align", skip_serializing_if = "Option::is_none")]
pub align: Option<Alignment>,
#[serde(rename = "@part", skip_serializing_if = "Option::is_none")]
pub part: Option<TablePart>,
#[serde(rename = "@role", skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
#[serde(rename = "@width", skip_serializing_if = "Option::is_none")]
pub width: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default)]
#[serde(rename = "row")]
pub struct TableRow {
#[serde(rename = "@align", skip_serializing_if = "Option::is_none")]
pub align: Option<Alignment>,
#[serde(rename = "@part", skip_serializing_if = "Option::is_none")]
pub part: Option<TablePart>,
#[serde(rename = "@role", skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
#[serde(rename = "cell", default)]
pub cells: Vec<TableCell>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default)]
#[serde(rename = "cell")]
pub struct TableCell {
#[serde(rename = "@align", skip_serializing_if = "Option::is_none")]
pub align: Option<Alignment>,
#[serde(rename = "@role", skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
#[serde(rename = "@colspan", skip_serializing_if = "Option::is_none")]
pub colspan: Option<u64>,
#[serde(rename = "@rowspan", skip_serializing_if = "Option::is_none")]
pub rowspan: Option<u64>,
#[serde(rename = "$value", default)]
pub content: Vec<CharacterStyle>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct Table {
pub caption: Option<TableCaption>,
#[serde(rename = "@role", skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
#[serde(rename = "@summary", skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[serde(rename = "@height", skip_serializing_if = "Option::is_none")]
pub height: Option<String>,
#[serde(rename = "@width", skip_serializing_if = "Option::is_none")]
pub width: Option<String>,
#[serde(rename = "col", default)]
pub cols: Vec<TableColumn>,
#[serde(rename = "row", default)]
pub rows: Vec<TableRow>,
}
impl Table {
pub fn basic(cols: usize, cells: Vec<Vec<String>>, caption: String) -> Self {
Table {
caption: Some(TableCaption { caption }),
role: None,
summary: None,
height: None,
width: None,
cols: (0..cols).map(|_| TableColumn::default()).collect(),
rows: cells
.into_iter()
.map(|row| TableRow {
cells: row
.into_iter()
.map(|cell| TableCell {
content: vec![CharacterStyle::Text(cell)],
..Default::default()
})
.collect(),
..Default::default()
})
.collect(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum FragmentContent {
#[serde(rename = "$text")]
Text(String),
Heading(Heading),
Block {
#[serde(rename = "$value", default)]
child: Vec<FragmentContent>,
},
BlockXRef(BlockXRef),
Para(Para),
Preformat {
#[serde(rename = "$value", default)]
child: Vec<CharacterStyle>,
},
Image(Image),
Table(Table),
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct Fragment {
#[serde(rename = "@id")]
pub id: String,
#[serde(rename = "@type")]
#[serde(skip_serializing_if = "Option::is_none")]
pub frag_type: Option<String>,
#[serde(rename = "@labels")]
#[serde(skip_serializing_if = "Option::is_none")]
pub labels: Option<String>,
#[serde(rename = "$value", default)]
pub content: Vec<FragmentContent>,
}
impl Fragment {
pub fn new(id: String) -> Fragment {
Fragment {
id,
frag_type: None,
labels: None,
content: vec![],
}
}
pub fn with_content(mut self, content: Vec<FragmentContent>) -> Fragment {
self.content.extend(content);
Fragment {
id: self.id,
frag_type: self.frag_type,
labels: self.labels,
content: self.content,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum Fragments {
#[serde(rename = "fragment")]
Fragment(Fragment),
#[serde(rename = "properties-fragment")]
Properties(PropertiesFragment),
#[serde(rename = "xref-fragment")]
Xref(XRefFragment),
#[serde(rename = "media-fragment")]
Media(()),
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum SectionContent {
#[serde(rename = "fragment")]
Fragment(Fragment),
#[serde(rename = "properties-fragment")]
PropertiesFragment(PropertiesFragment),
#[serde(rename = "xref-fragment")]
XRefFragment(XRefFragment),
#[serde(rename = "media-fragment")]
Media(()),
#[serde(rename = "title")]
Title {
#[serde(rename = "$text", default)]
text: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct Section {
#[serde(rename = "@id")]
pub id: String,
#[serde(rename = "@title")]
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(rename = "title")]
#[serde(skip_serializing_if = "Option::is_none")]
pub content_title: Option<String>,
#[serde(rename = "@edit")]
#[serde(skip_serializing_if = "Option::is_none")]
pub edit: Option<bool>,
#[serde(rename = "@lockstructure")]
#[serde(skip_serializing_if = "Option::is_none")]
pub lockstructure: Option<bool>,
#[serde(rename = "@overwrite")]
#[serde(skip_serializing_if = "Option::is_none")]
pub overwrite: Option<bool>,
#[serde(rename = "@fragmenttype")]
#[serde(skip_serializing_if = "Option::is_none")]
pub fragment_types: Option<String>,
#[serde(rename = "$value", default)]
pub content: Vec<SectionContent>,
}
impl Section {
pub fn new(id: String) -> Section {
Section {
id,
title: None,
content_title: None,
edit: Some(true),
lockstructure: Some(false),
overwrite: Some(true),
fragment_types: None,
content: Vec::new(),
}
}
pub fn add_fragment(&mut self, fragment: Fragments) {
self.content.push(match fragment {
Fragments::Fragment(frag) => SectionContent::Fragment(frag),
Fragments::Media(frag) => SectionContent::Media(frag),
Fragments::Properties(frag) => SectionContent::PropertiesFragment(frag),
Fragments::Xref(frag) => SectionContent::XRefFragment(frag),
});
}
pub fn with_fragments(mut self, fragments: Vec<Fragments>) -> Self {
for frag in fragments {
self.add_fragment(frag);
}
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct Publication {
pub id: String,
#[serde(rename = "@type")]
pub pub_type: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct Description {
#[serde(rename = "$value")]
pub value: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct URIDescriptor {
#[serde(rename = "@docid", skip_serializing_if = "Option::is_none")]
pub docid: Option<String>,
#[serde(rename = "@documenttype", skip_serializing_if = "Option::is_none")]
pub doc_type: Option<String>,
#[serde(rename = "@title", skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(rename = "@folder", skip_serializing_if = "Option::is_none")]
pub folder: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<Description>,
#[serde(skip_serializing_if = "Option::is_none")]
pub labels: Option<Labels>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct DocumentInfo {
#[serde(skip_serializing_if = "Option::is_none")]
pub uri: Option<URIDescriptor>,
#[serde(skip_serializing_if = "Option::is_none")]
pub publication: Option<Publication>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct Labels {
#[serde(rename = "$value", default)]
pub value: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct Content {
#[serde(rename = "$value")]
pub value: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct Note {
pub id: Option<String>,
#[serde(rename = "@title")]
pub title: Option<String>,
#[serde(rename = "@modified")]
pub modified: String,
pub labels: Labels,
pub content: Content,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct Notes {
pub notes: Vec<Note>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct Locator {
#[serde(rename = "fragment")]
pub fragment_id: Option<String>,
pub notes: Option<Notes>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct FragmentInfo {
pub value: Vec<Locator>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum DocumentLevel {
Metadata,
Portable,
Processed,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename = "document")]
pub struct Document {
#[serde(rename = "documentinfo")]
#[serde(skip_serializing_if = "Option::is_none")]
pub doc_info: Option<DocumentInfo>,
#[serde(rename = "fragmentinfo", default)]
pub frag_info: Vec<Locator>,
#[serde(rename = "section")]
pub sections: Vec<Section>,
#[serde(rename = "@type")]
#[serde(skip_serializing_if = "Option::is_none")]
pub doc_type: Option<String>,
#[serde(rename = "@edit")]
#[serde(skip_serializing_if = "Option::is_none")]
pub edit: Option<bool>,
#[serde(rename = "@level")]
pub level: DocumentLevel,
#[serde(rename = "@lockstructure")]
#[serde(skip_serializing_if = "Option::is_none")]
pub lockstructure: Option<bool>,
}
impl Document {
pub fn docid(&self) -> Option<&str> {
match &self.doc_info {
Some(doc_info) => match &doc_info.uri {
Some(uri) => match &uri.docid {
Some(docid) => Some(docid),
None => None,
},
None => None,
},
None => None,
}
}
pub fn get_section(&self, id: &str) -> Option<&Section> {
self.sections.iter().find(|§ion| section.id == id)
}
pub fn get_mut_section(&mut self, id: &str) -> Option<&mut Section> {
self.sections.iter_mut().find(|s| s.id == id)
}
}
impl Default for Document {
fn default() -> Self {
Self {
doc_info: None,
frag_info: vec![],
sections: vec![],
doc_type: None,
edit: None,
level: DocumentLevel::Portable,
lockstructure: None,
}
}
}