aboutsummaryrefslogtreecommitdiffstats
path: root/document_tree/src
diff options
context:
space:
mode:
Diffstat (limited to 'document_tree/src')
-rw-r--r--document_tree/src/attribute_types.rs155
-rw-r--r--document_tree/src/element_categories.rs130
-rw-r--r--document_tree/src/element_types.rs96
-rw-r--r--document_tree/src/elements.rs288
-rw-r--r--document_tree/src/extra_attributes.rs120
-rw-r--r--document_tree/src/lib.rs43
-rw-r--r--document_tree/src/macro_util.rs42
-rw-r--r--document_tree/src/url.rs78
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))
+ }
+}