diff options
Diffstat (limited to 'document_tree/src')
| -rw-r--r-- | document_tree/src/attribute_types.rs | 155 | ||||
| -rw-r--r-- | document_tree/src/element_categories.rs | 130 | ||||
| -rw-r--r-- | document_tree/src/element_types.rs | 96 | ||||
| -rw-r--r-- | document_tree/src/elements.rs | 288 | ||||
| -rw-r--r-- | document_tree/src/extra_attributes.rs | 120 | ||||
| -rw-r--r-- | document_tree/src/lib.rs | 43 | ||||
| -rw-r--r-- | document_tree/src/macro_util.rs | 42 | ||||
| -rw-r--r-- | document_tree/src/url.rs | 78 |
8 files changed, 952 insertions, 0 deletions
diff --git a/document_tree/src/attribute_types.rs b/document_tree/src/attribute_types.rs new file mode 100644 index 0000000..411b24d --- /dev/null +++ b/document_tree/src/attribute_types.rs @@ -0,0 +1,155 @@ +use std::str::FromStr; + +use failure::{Error,bail,format_err}; +use serde_derive::Serialize; +use regex::Regex; + +use crate::url::Url; + +#[derive(Debug,PartialEq,Eq,Hash,Serialize,Clone)] +pub enum EnumeratedListType { + Arabic, + LowerAlpha, + UpperAlpha, + LowerRoman, + UpperRoman, +} + +#[derive(Debug,PartialEq,Eq,Hash,Serialize,Clone)] +pub enum FixedSpace { Default, Preserve } // yes, default really is not “Default” +impl Default for FixedSpace { fn default() -> FixedSpace { FixedSpace::Preserve } } + +#[derive(Debug,PartialEq,Eq,Hash,Serialize,Clone)] pub enum AlignH { Left, Center, Right} +#[derive(Debug,PartialEq,Eq,Hash,Serialize,Clone)] pub enum AlignHV { Top, Middle, Bottom, Left, Center, Right } +#[derive(Debug,PartialEq,Eq,Hash,Serialize,Clone)] pub enum AlignV { Top, Middle, Bottom } + +#[derive(Debug,PartialEq,Eq,Hash,Serialize,Clone)] pub enum TableAlignH { Left, Right, Center, Justify, Char } +#[derive(Debug,PartialEq,Eq,Hash,Serialize,Clone)] pub enum TableBorder { Top, Bottom, TopBottom, All, Sides, None } + +#[derive(Debug,PartialEq,Eq,Hash,Serialize,Clone)] pub struct ID(pub String); +#[derive(Debug,PartialEq,Eq,Hash,Serialize,Clone)] pub struct NameToken(pub String); + +// The table DTD has the cols attribute of tgroup as required, but having +// TableGroupCols not implement Default would leave no possible implementation +// for TableGroup::with_children. +#[derive(Debug,PartialEq,Eq,Hash,Serialize,Clone)] pub struct TableGroupCols(pub usize); +impl Default for TableGroupCols { + fn default() -> Self { + TableGroupCols(0) + } +} + +// no eq for f64 +#[derive(Debug,PartialEq,Serialize,Clone)] +pub enum Measure { // http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#length-units + Em(f64), + Ex(f64), + Mm(f64), + Cm(f64), + In(f64), + Px(f64), + Pt(f64), + Pc(f64), +} + +impl FromStr for AlignHV { + type Err = Error; + fn from_str(s: &str) -> Result<Self, Self::Err> { + use self::AlignHV::*; + Ok(match s { + "top" => Top, + "middle" => Middle, + "bottom" => Bottom, + "left" => Left, + "center" => Center, + "right" => Right, + s => bail!("Invalid Alignment {}", s), + }) + } +} + +impl From<&str> for ID { + fn from(s: &str) -> Self { + ID(s.to_owned().replace(' ', "-")) + } +} + +impl From<&str> for NameToken { + fn from(s: &str) -> Self { + NameToken(s.to_owned()) + } +} + +impl FromStr for Measure { + type Err = Error; + fn from_str(s: &str) -> Result<Self, Self::Err> { + use self::Measure::*; + let re = Regex::new(r"(?P<float>\d+\.\d*|\.?\d+)\s*(?P<unit>em|ex|mm|cm|in|px|pt|pc)").unwrap(); + let caps: regex::Captures = re.captures(s).ok_or_else(|| format_err!("Invalid measure"))?; + let value: f64 = caps["float"].parse()?; + Ok(match &caps["unit"] { + "em" => Em(value), + "ex" => Ex(value), + "mm" => Mm(value), + "cm" => Cm(value), + "in" => In(value), + "px" => Px(value), + "pt" => Pt(value), + "pc" => Pc(value), + _ => unreachable!(), + }) + } +} + +#[cfg(test)] +mod parse_tests { + use super::*; + + #[test] + fn measure() { + let _a: Measure = "1.5em".parse().unwrap(); + let _b: Measure = "20 mm".parse().unwrap(); + let _c: Measure = ".5in".parse().unwrap(); + let _d: Measure = "1.pc".parse().unwrap(); + } +} + +pub(crate) trait CanBeEmpty { + fn is_empty(&self) -> bool; +} + +/* Specialization necessary +impl<T> CanBeEmpty for T { + fn is_empty(&self) -> bool { false } +} +*/ +macro_rules! impl_cannot_be_empty { + ($t:ty) => { + impl CanBeEmpty for $t { + fn is_empty(&self) -> bool { false } + } + }; + ($t:ty, $($ts:ty),*) => { + impl_cannot_be_empty!($t); + impl_cannot_be_empty!($($ts),*); + }; +} +impl_cannot_be_empty!(Url); +impl_cannot_be_empty!(TableGroupCols); + +impl<T> CanBeEmpty for Option<T> { + fn is_empty(&self) -> bool { self.is_none() } +} + +impl<T> CanBeEmpty for Vec<T> { + fn is_empty(&self) -> bool { self.is_empty() } +} + +impl CanBeEmpty for bool { + fn is_empty(&self) -> bool { !self } +} + +impl CanBeEmpty for FixedSpace { + fn is_empty(&self) -> bool { self == &FixedSpace::default() } +} + diff --git a/document_tree/src/element_categories.rs b/document_tree/src/element_categories.rs new file mode 100644 index 0000000..24a0798 --- /dev/null +++ b/document_tree/src/element_categories.rs @@ -0,0 +1,130 @@ +use std::fmt::{self,Debug,Formatter}; + +use serde_derive::Serialize; + +use crate::elements::*; + +pub trait HasChildren<C> { + fn with_children(children: Vec<C>) -> Self; + fn children(&self) -> &Vec<C>; + fn children_mut(&mut self) -> &mut Vec<C>; + fn append_child<R: Into<C>>(&mut self, child: R) { + self.children_mut().push(child.into()); + } + fn append_children<R: Into<C> + Clone>(&mut self, more: &[R]) { + let children = self.children_mut(); + children.reserve(more.len()); + for child in more { + children.push(child.clone().into()); + } + } +} + +macro_rules! impl_into { + ([ $( (($subcat:ident :: $entry:ident), $supcat:ident), )+ ]) => { + $( impl_into!($subcat::$entry => $supcat); )+ + }; + ($subcat:ident :: $entry:ident => $supcat:ident ) => { + impl Into<$supcat> for $entry { + fn into(self) -> $supcat { + $supcat::$subcat(Box::new(self.into())) + } + } + }; +} + +macro_rules! synonymous_enum { + ( $subcat:ident : $($supcat:ident),+ ; $midcat:ident : $supsupcat:ident { $($entry:ident),+ $(,)* } ) => { + synonymous_enum!($subcat : $( $supcat ),+ , $midcat { $($entry,)* }); + $( impl_into!($midcat::$entry => $supsupcat); )+ + }; + ( $subcat:ident : $($supcat:ident),+ { $($entry:ident),+ $(,)* } ) => { + synonymous_enum!($subcat { $( $entry, )* }); + cartesian!(impl_into, [ $( ($subcat::$entry) ),+ ], [ $($supcat),+ ]); + }; + ( $name:ident { $( $entry:ident ),+ $(,)* } ) => { + #[derive(PartialEq,Serialize,Clone)] + pub enum $name { $( + $entry(Box<$entry>), + )* } + + impl Debug for $name { + fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> { + match *self { + $( $name::$entry(ref inner) => inner.fmt(fmt), )* + } + } + } + + $( impl Into<$name> for $entry { + fn into(self) -> $name { + $name::$entry(Box::new(self)) + } + } )* + }; +} + +synonymous_enum!(StructuralSubElement { Title, Subtitle, Decoration, Docinfo, SubStructure }); +synonymous_enum!(SubStructure: StructuralSubElement { Topic, Sidebar, Transition, Section, BodyElement }); +synonymous_enum!(BodyElement: SubTopic, SubSidebar, SubBlockQuote, SubFootnote, SubFigure; SubStructure: StructuralSubElement { + //Simple + Paragraph, LiteralBlock, DoctestBlock, MathBlock, Rubric, SubstitutionDefinition, Comment, Pending, Target, Raw, Image, + //Compound + Compound, Container, + BulletList, EnumeratedList, DefinitionList, FieldList, OptionList, + LineBlock, BlockQuote, Admonition, Attention, Hint, Note, Caution, Danger, Error, Important, Tip, Warning, Footnote, Citation, SystemMessage, Figure, Table +}); + +synonymous_enum!(BibliographicElement { Author, Authors, Organization, Address, Contact, Version, Revision, Status, Date, Copyright, Field }); + +synonymous_enum!(TextOrInlineElement { + String, Emphasis, Strong, Literal, Reference, FootnoteReference, CitationReference, SubstitutionReference, TitleReference, Abbreviation, Acronym, Superscript, Subscript, Inline, Problematic, Generated, Math, + //also have non-inline versions. Inline image is no figure child, inline target has content + TargetInline, RawInline, ImageInline +}); + +//--------------\\ +//Content Models\\ +//--------------\\ + +synonymous_enum!(AuthorInfo { Author, Organization, Address, Contact }); +synonymous_enum!(DecorationElement { Header, Footer }); +synonymous_enum!(SubTopic { Title, BodyElement }); +synonymous_enum!(SubSidebar { Topic, Title, Subtitle, BodyElement }); +synonymous_enum!(SubDLItem { Term, Classifier, Definition }); +synonymous_enum!(SubField { FieldName, FieldBody }); +synonymous_enum!(SubOptionListItem { OptionGroup, Description }); +synonymous_enum!(SubOption { OptionString, OptionArgument }); +synonymous_enum!(SubLineBlock { LineBlock, Line }); +synonymous_enum!(SubBlockQuote { Attribution, BodyElement }); +synonymous_enum!(SubFootnote { Label, BodyElement }); +synonymous_enum!(SubFigure { Caption, Legend, BodyElement }); +synonymous_enum!(SubTable { Title, TableGroup }); +synonymous_enum!(SubTableGroup { TableColspec, TableHead, TableBody }); + +#[cfg(test)] +mod conversion_tests { + use std::default::Default; + use super::*; + + #[test] + fn basic() { + let _: BodyElement = Paragraph::default().into(); + } + + #[test] + fn more() { + let _: SubStructure = Paragraph::default().into(); + } + + #[test] + fn even_more() { + let _: StructuralSubElement = Paragraph::default().into(); + } + + #[test] + fn super_() { + let be: BodyElement = Paragraph::default().into(); + let _: StructuralSubElement = be.into(); + } +} diff --git a/document_tree/src/element_types.rs b/document_tree/src/element_types.rs new file mode 100644 index 0000000..429573e --- /dev/null +++ b/document_tree/src/element_types.rs @@ -0,0 +1,96 @@ + +// enum ElementType { +// //structual elements +// Section, Topic, Sidebar, +// +// //structural subelements +// Title, Subtitle, Decoration, Docinfo, Transition, +// +// //bibliographic elements +// Author, Authors, Organization, +// Address { space: FixedSpace }, +// Contact, Version, Revision, Status, +// Date, Copyright, Field, +// +// //decoration elements +// Header, Footer, +// +// //simple body elements +// Paragraph, +// LiteralBlock { space: FixedSpace }, +// DoctestBlock { space: FixedSpace }, +// MathBlock, Rubric, +// SubstitutionDefinition { ltrim: bool, rtrim: bool }, +// Comment { space: FixedSpace }, +// Pending, +// Target { refuri: Url, refid: ID, refname: Vec<NameToken>, anonymous: bool }, +// Raw { space: FixedSpace, format: Vec<NameToken> }, +// Image { +// align: AlignHV, +// uri: Url, +// alt: String, +// height: Measure, +// width: Measure, +// scale: f64, +// }, +// +// //compound body elements +// Compound, Container, +// +// BulletList { bullet: String }, +// EnumeratedList { enumtype: EnumeratedListType, prefix: String, suffix: String }, +// DefinitionList, FieldList, OptionList, +// +// LineBlock, BlockQuote, +// Admonition, Attention, Hint, Note, +// Caution, Danger, Error, Important, +// Tip, Warning, +// Footnote { backrefs: Vec<ID>, auto: bool }, +// Citation { backrefs: Vec<ID> }, +// SystemMessage { backrefs: Vec<ID>, level: usize, line: usize, type_: NameToken }, +// Figure { align: AlignH, width: usize }, +// Table, //TODO: Table +// +// //body sub elements +// ListItem, +// +// DefinitionListItem, Term, +// Classifier, Definition, +// +// FieldName, FieldBody, +// +// OptionListItem, OptionGroup, Description, Option_, OptionString, +// OptionArgument { delimiter: String }, +// +// Line, Attribution, Label, +// +// Caption, Legend, +// +// //inline elements +// Emphasis, Strong, Literal, +// Reference { name: String, refuri: Url, refid: ID, refname: Vec<NameToken> }, +// FootnoteReference { refid: ID, refname: Vec<NameToken>, auto: bool }, +// CitationReference { refid: ID, refname: Vec<NameToken> }, +// SubstitutionReference { refname: Vec<NameToken> }, +// TitleReference, +// Abbreviation, Acronym, +// Superscript, Subscript, +// Inline, +// Problematic { refid: ID }, +// Generated, Math, +// +// //also have non-inline versions. Inline image is no figure child, inline target has content +// TargetInline { refuri: Url, refid: ID, refname: Vec<NameToken>, anonymous: bool }, +// RawInline { space: FixedSpace, format: Vec<NameToken> }, +// ImageInline { +// align: AlignHV, +// uri: Url, +// alt: String, +// height: Measure, +// width: Measure, +// scale: f64, +// }, +// +// //text element +// TextElement, +// } diff --git a/document_tree/src/elements.rs b/document_tree/src/elements.rs new file mode 100644 index 0000000..26bccf6 --- /dev/null +++ b/document_tree/src/elements.rs @@ -0,0 +1,288 @@ +use std::path::PathBuf; +use serde_derive::Serialize; + +use crate::attribute_types::{CanBeEmpty,ID,NameToken}; +use crate::extra_attributes::{self,ExtraAttributes}; +use crate::element_categories::*; + + +//-----------------\\ +//Element hierarchy\\ +//-----------------\\ + +pub trait Element { + /// A list containing one or more unique identifier keys + fn ids (& self) -> & Vec<ID>; + fn ids_mut(&mut self) -> &mut Vec<ID>; + /// a list containing the names of an element, typically originating from the element's title or content. + /// Each name in names must be unique; if there are name conflicts (two or more elements want to the same name), + /// the contents will be transferred to the dupnames attribute on the duplicate elements. + /// An element may have at most one of the names or dupnames attributes, but not both. + fn names (& self) -> & Vec<NameToken>; + fn names_mut(&mut self) -> &mut Vec<NameToken>; + fn source (& self) -> & Option<PathBuf>; + fn source_mut(&mut self) -> &mut Option<PathBuf>; + fn classes (& self) -> & Vec<String>; + fn classes_mut(&mut self) -> &mut Vec<String>; +} + +#[derive(Debug,Default,PartialEq,Serialize,Clone)] +pub struct CommonAttributes { + #[serde(skip_serializing_if = "CanBeEmpty::is_empty")] + ids: Vec<ID>, + #[serde(skip_serializing_if = "CanBeEmpty::is_empty")] + names: Vec<NameToken>, + #[serde(skip_serializing_if = "CanBeEmpty::is_empty")] + source: Option<PathBuf>, + #[serde(skip_serializing_if = "CanBeEmpty::is_empty")] + classes: Vec<String>, + //TODO: dupnames +} + +//----\\ +//impl\\ +//----\\ + +macro_rules! impl_element { ($name:ident) => ( + impl Element for $name { + fn ids (& self) -> & Vec<ID> { & self.common.ids } + fn ids_mut(&mut self) -> &mut Vec<ID> { &mut self.common.ids } + fn names (& self) -> & Vec<NameToken> { & self.common.names } + fn names_mut(&mut self) -> &mut Vec<NameToken> { &mut self.common.names } + fn source (& self) -> & Option<PathBuf> { & self.common.source } + fn source_mut(&mut self) -> &mut Option<PathBuf> { &mut self.common.source } + fn classes (& self) -> & Vec<String> { & self.common.classes } + fn classes_mut(&mut self) -> &mut Vec<String> { &mut self.common.classes } + } +)} + +macro_rules! impl_children { ($name:ident, $childtype:ident) => ( + impl HasChildren<$childtype> for $name { + #[allow(clippy::needless_update)] + fn with_children(children: Vec<$childtype>) -> $name { $name { children: children, ..Default::default() } } + fn children (& self) -> & Vec<$childtype> { & self.children } + fn children_mut(&mut self) -> &mut Vec<$childtype> { &mut self.children } + } +)} + +macro_rules! impl_extra { ($name:ident $($more:tt)*) => ( + impl ExtraAttributes<extra_attributes::$name> for $name { + #[allow(clippy::needless_update)] + fn with_extra(extra: extra_attributes::$name) -> $name { $name { common: Default::default(), extra: extra $($more)* } } + fn extra (& self) -> & extra_attributes::$name { & self.extra } + fn extra_mut(&mut self) -> &mut extra_attributes::$name { &mut self.extra } + } +)} + +trait HasExtraAndChildren<C, A> { + fn with_extra_and_children(extra: A, children: Vec<C>) -> Self; +} + +impl<T, C, A> HasExtraAndChildren<C, A> for T where T: HasChildren<C> + ExtraAttributes<A> { + #[allow(clippy::needless_update)] + fn with_extra_and_children(extra: A, mut children: Vec<C>) -> Self { + let mut r = Self::with_extra(extra); + r.children_mut().extend(children.drain(..)); + r + } +} + +macro_rules! impl_new {( + $(#[$attr:meta])* + pub struct $name:ident { $( + $(#[$fattr:meta])* + $field:ident : $typ:path + ),* $(,)* } +) => ( + $(#[$attr])* + #[derive(Debug,PartialEq,Serialize,Clone)] + pub struct $name { $( + $(#[$fattr])* $field: $typ, + )* } + impl $name { + pub fn new( $( $field: $typ, )* ) -> $name { $name { $( $field: $field, )* } } + } +)} + +macro_rules! impl_elem { + ($name:ident) => { + impl_new!(#[derive(Default)] pub struct $name { + #[serde(flatten)] common: CommonAttributes, + }); + impl_element!($name); + }; + ($name:ident; +) => { + impl_new!(#[derive(Default)] pub struct $name { + #[serde(flatten)] common: CommonAttributes, + #[serde(flatten)] extra: extra_attributes::$name, + }); + impl_element!($name); impl_extra!($name, ..Default::default()); + }; + ($name:ident; *) => { //same as above with no default + impl_new!(pub struct $name { + #[serde(flatten)] common: CommonAttributes, + #[serde(flatten)] extra: extra_attributes::$name + }); + impl_element!($name); impl_extra!($name); + }; + ($name:ident, $childtype:ident) => { + impl_new!(#[derive(Default)] pub struct $name { + #[serde(flatten)] common: CommonAttributes, + children: Vec<$childtype>, + }); + impl_element!($name); impl_children!($name, $childtype); + }; + ($name:ident, $childtype:ident; +) => { + impl_new!(#[derive(Default)] pub struct $name { + #[serde(flatten)] common: CommonAttributes, + #[serde(flatten)] extra: extra_attributes::$name, + children: Vec<$childtype>, + }); + impl_element!($name); impl_extra!($name, ..Default::default()); impl_children!($name, $childtype); + }; +} + +macro_rules! impl_elems { ( $( ($($args:tt)*) )* ) => ( + $( impl_elem!($($args)*); )* +)} + + +#[derive(Default,Debug,Serialize)] +pub struct Document { children: Vec<StructuralSubElement> } +impl_children!(Document, StructuralSubElement); + +impl_elems!( + //structual elements + (Section, StructuralSubElement) + (Topic, SubTopic) + (Sidebar, SubSidebar) + + //structural subelements + (Title, TextOrInlineElement) + (Subtitle, TextOrInlineElement) + (Decoration, DecorationElement) + (Docinfo, BibliographicElement) + (Transition) + + //bibliographic elements + (Author, TextOrInlineElement) + (Authors, AuthorInfo) + (Organization, TextOrInlineElement) + (Address, TextOrInlineElement; +) + (Contact, TextOrInlineElement) + (Version, TextOrInlineElement) + (Revision, TextOrInlineElement) + (Status, TextOrInlineElement) + (Date, TextOrInlineElement) + (Copyright, TextOrInlineElement) + (Field, SubField) + + //decoration elements + (Header, BodyElement) + (Footer, BodyElement) + + //simple body elements + (Paragraph, TextOrInlineElement) + (LiteralBlock, TextOrInlineElement; +) + (DoctestBlock, TextOrInlineElement; +) + (MathBlock, String) + (Rubric, TextOrInlineElement) + (SubstitutionDefinition, TextOrInlineElement; +) + (Comment, TextOrInlineElement; +) + (Pending) + (Target; +) + (Raw, String; +) + (Image; *) + + //compound body elements + (Compound, BodyElement) + (Container, BodyElement) + + (BulletList, ListItem; +) + (EnumeratedList, ListItem; +) + (DefinitionList, DefinitionListItem) + (FieldList, Field) + (OptionList, OptionListItem) + + (LineBlock, SubLineBlock) + (BlockQuote, SubBlockQuote) + (Admonition, SubTopic) + (Attention, BodyElement) + (Hint, BodyElement) + (Note, BodyElement) + (Caution, BodyElement) + (Danger, BodyElement) + (Error, BodyElement) + (Important, BodyElement) + (Tip, BodyElement) + (Warning, BodyElement) + (Footnote, SubFootnote; +) + (Citation, SubFootnote; +) + (SystemMessage, BodyElement; +) + (Figure, SubFigure; +) + (Table, SubTable; +) + + //table elements + (TableGroup, SubTableGroup; +) + (TableHead, TableRow; +) + (TableBody, TableRow; +) + (TableRow, TableEntry; +) + (TableEntry, BodyElement; +) + (TableColspec; +) + + //body sub elements + (ListItem, BodyElement) + + (DefinitionListItem, SubDLItem) + (Term, TextOrInlineElement) + (Classifier, TextOrInlineElement) + (Definition, BodyElement) + + (FieldName, TextOrInlineElement) + (FieldBody, BodyElement) + + (OptionListItem, SubOptionListItem) + (OptionGroup, Option_) + (Description, BodyElement) + (Option_, SubOption) + (OptionString, String) + (OptionArgument, String; +) + + (Line, TextOrInlineElement) + (Attribution, TextOrInlineElement) + (Label, TextOrInlineElement) + + (Caption, TextOrInlineElement) + (Legend, BodyElement) + + //inline elements + (Emphasis, TextOrInlineElement) + (Literal, TextOrInlineElement) + (Reference, TextOrInlineElement; +) + (Strong, TextOrInlineElement) + (FootnoteReference, TextOrInlineElement; +) + (CitationReference, TextOrInlineElement; +) + (SubstitutionReference, TextOrInlineElement; +) + (TitleReference, TextOrInlineElement) + (Abbreviation, TextOrInlineElement) + (Acronym, TextOrInlineElement) + (Superscript, TextOrInlineElement) + (Subscript, TextOrInlineElement) + (Inline, TextOrInlineElement) + (Problematic, TextOrInlineElement; +) + (Generated, TextOrInlineElement) + (Math, String) + + //also have non-inline versions. Inline image is no figure child, inline target has content + (TargetInline, String; +) + (RawInline, String; +) + (ImageInline; *) + + //text element = String +); + +impl<'a> From<&'a str> for TextOrInlineElement { + fn from(s: &'a str) -> Self { + s.to_owned().into() + } +} diff --git a/document_tree/src/extra_attributes.rs b/document_tree/src/extra_attributes.rs new file mode 100644 index 0000000..45fcf32 --- /dev/null +++ b/document_tree/src/extra_attributes.rs @@ -0,0 +1,120 @@ +use serde_derive::Serialize; + +use crate::url::Url; +use crate::attribute_types::{ + CanBeEmpty, + FixedSpace, + ID,NameToken, + AlignHV,AlignH,AlignV, + TableAlignH,TableBorder,TableGroupCols, + Measure, + EnumeratedListType, +}; + +pub trait ExtraAttributes<A> { + fn with_extra(extra: A) -> Self; + fn extra (& self) -> & A; + fn extra_mut(&mut self) -> &mut A; +} + +macro_rules! impl_extra { + ( $name:ident { $( $(#[$pattr:meta])* $param:ident : $type:ty ),* $(,)* } ) => ( + impl_extra!( + #[derive(Default,Debug,PartialEq,Serialize,Clone)] + $name { $( $(#[$pattr])* $param : $type, )* } + ); + ); + ( $(#[$attr:meta])+ $name:ident { $( $(#[$pattr:meta])* $param:ident : $type:ty ),* $(,)* } ) => ( + $(#[$attr])+ + pub struct $name { $( + $(#[$pattr])* + #[serde(skip_serializing_if = "CanBeEmpty::is_empty")] + pub $param : $type, + )* } + ); +} + +impl_extra!(Address { space: FixedSpace }); +impl_extra!(LiteralBlock { space: FixedSpace }); +impl_extra!(DoctestBlock { space: FixedSpace }); +impl_extra!(SubstitutionDefinition { ltrim: bool, rtrim: bool }); +impl_extra!(Comment { space: FixedSpace }); +impl_extra!(Target { + /// External reference to a URI/URL + refuri: Option<Url>, + /// References to ids attributes in other elements + refid: Option<ID>, + /// Internal reference to the names attribute of another element. May resolve to either an internal or external reference. + refname: Vec<NameToken>, + anonymous: bool, +}); +impl_extra!(Raw { space: FixedSpace, format: Vec<NameToken> }); +impl_extra!(#[derive(Debug,PartialEq,Serialize,Clone)] Image { + uri: Url, + align: Option<AlignHV>, + alt: Option<String>, + height: Option<Measure>, + width: Option<Measure>, + scale: Option<u8>, + target: Option<Url>, // Not part of the DTD but a valid argument +}); + +//bools usually are XML yesorno. “auto” however either exists and is set to something random like “1” or doesn’t exist +//does auto actually mean the numbering prefix? + +impl_extra!(BulletList { bullet: Option<String> }); +impl_extra!(EnumeratedList { enumtype: Option<EnumeratedListType>, prefix: Option<String>, suffix: Option<String> }); + +impl_extra!(Footnote { backrefs: Vec<ID>, auto: bool }); +impl_extra!(Citation { backrefs: Vec<ID> }); +impl_extra!(SystemMessage { backrefs: Vec<ID>, level: Option<usize>, line: Option<usize>, type_: Option<NameToken> }); +impl_extra!(Figure { align: Option<AlignH>, width: Option<usize> }); +impl_extra!(Table { frame: Option<TableBorder>, colsep: Option<bool>, rowsep: Option<bool>, pgwide: Option<bool> }); + +impl_extra!(TableGroup { cols: TableGroupCols, colsep: Option<bool>, rowsep: Option<bool>, align: Option<TableAlignH> }); +impl_extra!(TableHead { valign: Option<AlignV> }); +impl_extra!(TableBody { valign: Option<AlignV> }); +impl_extra!(TableRow { rowsep: Option<bool>, valign: Option<AlignV> }); +impl_extra!(TableEntry { colname: Option<NameToken>, namest: Option<NameToken>, nameend: Option<NameToken>, morerows: Option<usize>, colsep: Option<bool>, rowsep: Option<bool>, align: Option<TableAlignH>, r#char: Option<char>, charoff: Option<usize>, valign: Option<AlignV>, morecols: Option<usize> }); +impl_extra!(TableColspec { colnum: Option<usize>, colname: Option<NameToken>, colwidth: Option<String>, colsep: Option<bool>, rowsep: Option<bool>, align: Option<TableAlignH>, r#char: Option<char>, charoff: Option<usize>, stub: Option<bool> }); + +impl_extra!(OptionArgument { delimiter: Option<String> }); + +impl_extra!(Reference { + name: Option<NameToken>, //TODO: is CDATA in the DTD, so maybe no nametoken? + /// External reference to a URI/URL + refuri: Option<Url>, + /// References to ids attributes in other elements + refid: Option<ID>, + /// Internal reference to the names attribute of another element + refname: Vec<NameToken>, +}); +impl_extra!(FootnoteReference { refid: Option<ID>, refname: Vec<NameToken>, auto: bool }); +impl_extra!(CitationReference { refid: Option<ID>, refname: Vec<NameToken> }); +impl_extra!(SubstitutionReference { refname: Vec<NameToken> }); +impl_extra!(Problematic { refid: Option<ID> }); + +//also have non-inline versions. Inline image is no figure child, inline target has content +impl_extra!(TargetInline { + /// External reference to a URI/URL + refuri: Option<Url>, + /// References to ids attributes in other elements + refid: Option<ID>, + /// Internal reference to the names attribute of another element. May resolve to either an internal or external reference. + refname: Vec<NameToken>, + anonymous: bool, +}); +impl_extra!(RawInline { space: FixedSpace, format: Vec<NameToken> }); +pub type ImageInline = Image; + +impl Image { + pub fn new(uri: Url) -> Image { Image { + uri, + align: None, + alt: None, + height: None, + width: None, + scale: None, + target: None, + } } +} diff --git a/document_tree/src/lib.rs b/document_tree/src/lib.rs new file mode 100644 index 0000000..324fc44 --- /dev/null +++ b/document_tree/src/lib.rs @@ -0,0 +1,43 @@ +#![recursion_limit="256"] + +///http://docutils.sourceforge.net/docs/ref/doctree.html +///serves as AST + +#[macro_use] +mod macro_util; + +pub mod url; +pub mod elements; +pub mod element_categories; +pub mod extra_attributes; +pub mod attribute_types; + +pub use self::elements::*; //Element,CommonAttributes,HasExtraAndChildren +pub use self::extra_attributes::ExtraAttributes; +pub use self::element_categories::HasChildren; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn imperative() { + let mut doc = Document::default(); + let mut title = Title::default(); + title.append_child("Hi"); + doc.append_child(title); + + println!("{:?}", doc); + } + + #[test] + fn descriptive() { + let doc = Document::with_children(vec![ + Title::with_children(vec![ + "Hi".into() + ]).into() + ]); + + println!("{:?}", doc); + } +} diff --git a/document_tree/src/macro_util.rs b/document_tree/src/macro_util.rs new file mode 100644 index 0000000..dcf3725 --- /dev/null +++ b/document_tree/src/macro_util.rs @@ -0,0 +1,42 @@ +macro_rules! cartesian_impl { + ($out:tt [] $b:tt $init_b:tt $submacro:tt) => { + $submacro!{$out} + }; + ($out:tt [$a:tt, $($at:tt)*] [] $init_b:tt $submacro:tt) => { + cartesian_impl!{$out [$($at)*] $init_b $init_b $submacro} + }; + ([$($out:tt)*] [$a:tt, $($at:tt)*] [$b:tt, $($bt:tt)*] $init_b:tt $submacro:tt) => { + cartesian_impl!{[$($out)* ($a, $b),] [$a, $($at)*] [$($bt)*] $init_b $submacro} + }; +} + +macro_rules! cartesian { + ( $submacro:tt, [$($a:tt)*], [$($b:tt)*]) => { + cartesian_impl!{[] [$($a)*,] [$($b)*,] [$($b)*,] $submacro} + }; +} + + +#[cfg(test)] +mod tests { + macro_rules! print_cartesian { + ( [ $(($a1:tt, $a2:tt)),* , ] ) => { + fn test_f(x:i64, y:i64) -> Result<(i64, i64), ()> { + match (x, y) { + $( + ($a1, $a2) => { Ok(($a1, $a2)) } + )* + _ => { Err(()) } + } + } + }; + } + + #[test] + fn print_cartesian() { + cartesian!(print_cartesian, [1, 2, 3], [4, 5, 6]); + assert_eq!(test_f(1, 4), Ok((1, 4))); + assert_eq!(test_f(1, 3), Err(())); + assert_eq!(test_f(3, 5), Ok((3, 5))); + } +} diff --git a/document_tree/src/url.rs b/document_tree/src/url.rs new file mode 100644 index 0000000..31a0536 --- /dev/null +++ b/document_tree/src/url.rs @@ -0,0 +1,78 @@ +use std::fmt; +use std::str::FromStr; + +use url::{self,ParseError}; +use serde_derive::Serialize; + + +fn starts_with_scheme(input: &str) -> bool { + let scheme = input.split(':').next().unwrap(); + if scheme == input || scheme.is_empty() { + return false; + } + let mut chars = input.chars(); + // First character. + if !chars.next().unwrap().is_ascii_alphabetic() { + return false; + } + for ch in chars { + if !ch.is_ascii_alphanumeric() && ch != '+' && ch != '-' && ch != '.' { + return false; + } + } + true +} + +/// The string representation of a URL, either absolute or relative, that has +/// been verified as a valid URL on construction. +#[derive(Debug,PartialEq,Serialize,Clone)] +#[serde(transparent)] +pub struct Url(String); + +impl Url { + pub fn parse_absolute(input: &str) -> Result<Self, ParseError> { + Ok(url::Url::parse(input)?.into()) + } + pub fn parse_relative(input: &str) -> Result<Self, ParseError> { + // We're assuming that any scheme through which RsT documents are being + // accessed is a hierarchical scheme, and so we can parse relative to a + // random hierarchical URL. + if input.starts_with('/') || !starts_with_scheme(input) { + // Continue only if the parse succeeded, disregarding its result. + let random_base_url = url::Url::parse("https://a/b").unwrap(); + url::Url::options() + .base_url(Some(&random_base_url)) + .parse(input)?; + Ok(Url(input.into())) + } else { + // If this is a URL at all, it's an absolute one. + // There's no appropriate variant of url::ParseError really. + Err(ParseError::SetHostOnCannotBeABaseUrl) + } + } + pub fn as_str(&self) -> &str { + self.0.as_str() + } +} + +impl From<url::Url> for Url { + fn from(url: url::Url) -> Self { + Url(url.into_string()) + } +} + + +impl fmt::Display for Url { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + + +impl FromStr for Url { + type Err = ParseError; + fn from_str(input: &str) -> Result<Self, Self::Err> { + Url::parse_absolute(input) + .or_else(|_| Url::parse_relative(input)) + } +} |
