document_tree-0.4.1/.cargo_vcs_info.json0000644000000001530000000000100136720ustar { "git": { "sha1": "e5ae59b41f533a7a0a48e8426ca9d440332d0eb0" }, "path_in_vcs": "document_tree" }document_tree-0.4.1/CHANGELOG.md000064400000000000000000000014601046102023000142750ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [0.4.1](https://github.com/flying-sheep/rust-rst/compare/document_tree-v0.4.0...document_tree-v0.4.1) - 2024-11-20 ### Other - Bump deps and refresh token ([#59](https://github.com/flying-sheep/rust-rst/pull/59)) - Bump serde-xml-rs to 0.5 ([#51](https://github.com/flying-sheep/rust-rst/pull/51)) - Migrate from quicli/failure to plain clap/anyhow ([#50](https://github.com/flying-sheep/rust-rst/pull/50)) - Format ([#38](https://github.com/flying-sheep/rust-rst/pull/38)) - Add CI ([#36](https://github.com/flying-sheep/rust-rst/pull/36)) document_tree-0.4.1/Cargo.toml0000644000000022670000000000100117000ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "document_tree" version = "0.4.1" authors = ["Philipp A. "] build = false autobins = false autoexamples = false autotests = false autobenches = false description = "reStructuredText’s DocumentTree representation" homepage = "https://github.com/flying-sheep/rust-rst" documentation = "https://docs.rs/document_tree" readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://github.com/flying-sheep/rust-rst" [lib] name = "document_tree" path = "src/lib.rs" [dependencies.anyhow] version = "1.0.86" [dependencies.regex] version = "1.5.5" [dependencies.serde] version = "1.0.104" [dependencies.serde_derive] version = "1.0.104" [dependencies.url] version = "2.1.0" document_tree-0.4.1/Cargo.toml.orig000064400000000000000000000007611046102023000153560ustar 00000000000000[package] name = 'document_tree' version = "0.4.1" authors = ['Philipp A. '] edition = '2018' description = 'reStructuredText’s DocumentTree representation' license = 'MIT OR Apache-2.0' readme = 'README.md' documentation = 'https://docs.rs/document_tree' homepage = 'https://github.com/flying-sheep/rust-rst' repository = 'https://github.com/flying-sheep/rust-rst' [dependencies] anyhow = '1.0.86' regex = '1.5.5' url = '2.1.0' serde = '1.0.104' serde_derive = '1.0.104' document_tree-0.4.1/README.md000064400000000000000000000036021046102023000137430ustar 00000000000000`document_tree` =============== Part of the [`rst`][rst] crate family. This crate contains structs and traits mirroring [Docutils’ Document Tree][doctree] model. The basic structure is a tree of [elements][], some of which [have children][] and/or [extra attributes][]. ```rust use document_tree::*; use document_tree::{extra_attributes as a, element_categories as c, attribute_types as t}; #[test] fn imperative() { let mut doc = Document::default(); let mut title = Title::default(); let url = "https://example.com/image.jpg".parse().unwrap(); let image = ImageInline::with_extra(a::ImageInline::new(url)); title.append_child("Hi"); title.append_child(image); doc.append_child(title); println!("{:?}", doc); } #[test] fn descriptive() { let doc = Document::with_children(vec![ Title::with_children(vec![ "Hi".into(), ImageInline::with_extra(a::ImageInline::new( "https://example.com/image.jpg".parse().unwrap() )).into(), ]).into() ]); println!("{:?}", doc); } ``` Check out the other crates in the family on how to create one from rST markup or render it! The advantages of this approach are that it’s convenient to have the children interface, as well as to trivially map elements to XML. The disadvantage is that a “vector of children” is not a well-defined model for the more structured elements like e.g. a section, which always contains a title followed by blocks. [rst]: https://github.com/flying-sheep/rust-rst/#readme [doctree]: https://docutils.sourceforge.io/docs/ref/doctree.html [elements]: https://docs.rs/document_tree/0/document_tree/elements/trait.Element.html [have children]: https://docs.rs/document_tree/0/document_tree/element_categories/trait.HasChildren.html [extra attributes]: https://docs.rs/document_tree/0/document_tree/extra_attributes/trait.ExtraAttributes.html document_tree-0.4.1/src/attribute_types.rs000064400000000000000000000105201046102023000170450ustar 00000000000000use std::str::FromStr; use anyhow::{bail, format_err, Error}; use regex::Regex; use serde_derive::Serialize; use crate::url::Url; #[derive(Debug, PartialEq, Eq, Hash, Serialize, Clone)] pub enum EnumeratedListType { Arabic, LowerAlpha, UpperAlpha, LowerRoman, UpperRoman, } #[derive(Default, Debug, PartialEq, Eq, Hash, Serialize, Clone)] pub enum FixedSpace { Default, // yes, default really is not “Default” #[default] 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(Default, Debug, PartialEq, Eq, Hash, Serialize, Clone)] pub struct TableGroupCols(pub usize); // 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 { 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 { use self::Measure::*; let re = Regex::new(r"(?P\d+\.\d*|\.?\d+)\s*(?Pem|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 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 CanBeEmpty for Option { fn is_empty(&self) -> bool { self.is_none() } } impl CanBeEmpty for Vec { 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() } } document_tree-0.4.1/src/element_categories.rs000064400000000000000000000116601046102023000174620ustar 00000000000000use std::fmt::{self, Debug, Formatter}; use serde_derive::Serialize; use crate::elements::*; pub trait HasChildren { fn with_children(children: Vec) -> Self; fn children(&self) -> &Vec; fn children_mut(&mut self) -> &mut Vec; fn append_child>(&mut self, child: R) { self.children_mut().push(child.into()); } fn append_children + 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 From<$entry> for $supcat { fn from(inner: $entry) -> Self { $supcat::$subcat(Box::new(inner.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 From<$entry> for $name { fn from(inner: $entry) -> Self { $name::$entry(Box::new(inner)) } } )* }; } 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 super::*; use std::default::Default; #[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(); } } document_tree-0.4.1/src/element_types.rs000064400000000000000000000056161046102023000165050ustar 00000000000000 // 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, anonymous: bool }, // Raw { space: FixedSpace, format: Vec }, // 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, auto: bool }, // Citation { backrefs: Vec }, // SystemMessage { backrefs: Vec, 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 }, // FootnoteReference { refid: ID, refname: Vec, auto: bool }, // CitationReference { refid: ID, refname: Vec }, // SubstitutionReference { refname: Vec }, // 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, anonymous: bool }, // RawInline { space: FixedSpace, format: Vec }, // ImageInline { // align: AlignHV, // uri: Url, // alt: String, // height: Measure, // width: Measure, // scale: f64, // }, // // //text element // TextElement, // } document_tree-0.4.1/src/elements.rs000064400000000000000000000246411046102023000154430ustar 00000000000000use serde_derive::Serialize; use std::path::PathBuf; use crate::attribute_types::{CanBeEmpty, NameToken, ID}; use crate::element_categories::*; use crate::extra_attributes::{self, ExtraAttributes}; //-----------------\\ //Element hierarchy\\ //-----------------\\ pub trait Element { /// A list containing one or more unique identifier keys fn ids(&self) -> &Vec; fn ids_mut(&mut self) -> &mut Vec; /// 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; fn names_mut(&mut self) -> &mut Vec; fn source(&self) -> &Option; fn source_mut(&mut self) -> &mut Option; fn classes(&self) -> &Vec; fn classes_mut(&mut self) -> &mut Vec; } #[derive(Debug, Default, PartialEq, Serialize, Clone)] pub struct CommonAttributes { #[serde(skip_serializing_if = "CanBeEmpty::is_empty")] ids: Vec, #[serde(skip_serializing_if = "CanBeEmpty::is_empty")] names: Vec, #[serde(skip_serializing_if = "CanBeEmpty::is_empty")] source: Option, #[serde(skip_serializing_if = "CanBeEmpty::is_empty")] classes: Vec, //TODO: dupnames } //----\\ //impl\\ //----\\ macro_rules! impl_element { ($name:ident) => { impl Element for $name { fn ids(&self) -> &Vec { &self.common.ids } fn ids_mut(&mut self) -> &mut Vec { &mut self.common.ids } fn names(&self) -> &Vec { &self.common.names } fn names_mut(&mut self) -> &mut Vec { &mut self.common.names } fn source(&self) -> &Option { &self.common.source } fn source_mut(&mut self) -> &mut Option { &mut self.common.source } fn classes(&self) -> &Vec { &self.common.classes } fn classes_mut(&mut self) -> &mut Vec { &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, ..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 for $name { #[allow(clippy::needless_update)] fn with_extra(extra: extra_attributes::$name) -> $name { $name { common: Default::default(), extra $($more)* } } fn extra (& self) -> & extra_attributes::$name { & self.extra } fn extra_mut(&mut self) -> &mut extra_attributes::$name { &mut self.extra } } )} #[allow(dead_code)] trait HasExtraAndChildren { fn with_extra_and_children(extra: A, children: Vec) -> Self; } impl HasExtraAndChildren for T where T: HasChildren + ExtraAttributes, { #[allow(clippy::needless_update)] fn with_extra_and_children(extra: A, mut children: Vec) -> Self { let mut r = Self::with_extra(extra); r.children_mut().append(&mut children); 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, )* } } } )} 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, } 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, String) (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() } } document_tree-0.4.1/src/extra_attributes.rs000064400000000000000000000115661046102023000172220ustar 00000000000000use serde_derive::Serialize; use crate::attribute_types::{ AlignH, AlignHV, AlignV, CanBeEmpty, EnumeratedListType, FixedSpace, Measure, NameToken, TableAlignH, TableBorder, TableGroupCols, ID, }; use crate::url::Url; pub trait ExtraAttributes { 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, /// References to ids attributes in other elements refid: Option, /// Internal reference to the names attribute of another element. May resolve to either an internal or external reference. refname: Vec, anonymous: bool, }); impl_extra!(Raw { space: FixedSpace, format: Vec }); impl_extra!(#[derive(Debug,PartialEq,Serialize,Clone)] Image { uri: Url, align: Option, alt: Option, height: Option, width: Option, scale: Option, target: Option, // 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 }); impl_extra!(EnumeratedList { enumtype: Option, prefix: Option, suffix: Option }); impl_extra!(Footnote { backrefs: Vec, auto: bool }); impl_extra!(Citation { backrefs: Vec }); impl_extra!(SystemMessage { backrefs: Vec, level: Option, line: Option, type_: Option }); impl_extra!(Figure { align: Option, width: Option }); impl_extra!(Table { frame: Option, colsep: Option, rowsep: Option, pgwide: Option }); impl_extra!(TableGroup { cols: TableGroupCols, colsep: Option, rowsep: Option, align: Option }); impl_extra!(TableHead { valign: Option }); impl_extra!(TableBody { valign: Option }); impl_extra!(TableRow { rowsep: Option, valign: Option }); impl_extra!(TableEntry { colname: Option, namest: Option, nameend: Option, morerows: Option, colsep: Option, rowsep: Option, align: Option, r#char: Option, charoff: Option, valign: Option, morecols: Option }); impl_extra!(TableColspec { colnum: Option, colname: Option, colwidth: Option, colsep: Option, rowsep: Option, align: Option, r#char: Option, charoff: Option, stub: Option }); impl_extra!(OptionArgument { delimiter: Option }); impl_extra!(Reference { name: Option, //TODO: is CDATA in the DTD, so maybe no nametoken? /// External reference to a URI/URL refuri: Option, /// References to ids attributes in other elements refid: Option, /// Internal reference to the names attribute of another element refname: Vec, }); impl_extra!(FootnoteReference { refid: Option, refname: Vec, auto: bool }); impl_extra!(CitationReference { refid: Option, refname: Vec }); impl_extra!(SubstitutionReference { refname: Vec }); impl_extra!(Problematic { refid: Option }); //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, /// References to ids attributes in other elements refid: Option, /// Internal reference to the names attribute of another element. May resolve to either an internal or external reference. refname: Vec, anonymous: bool, }); impl_extra!(RawInline { space: FixedSpace, format: Vec }); 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, } } } document_tree-0.4.1/src/lib.rs000064400000000000000000000024641046102023000143740ustar 00000000000000#![recursion_limit = "256"] /// See [doctree][] reference. /// Serves as AST. /// /// [doctree]: http://docutils.sourceforge.net/docs/ref/doctree.html #[macro_use] mod macro_util; pub mod attribute_types; pub mod element_categories; pub mod elements; pub mod extra_attributes; pub mod url; pub use self::element_categories::HasChildren; pub use self::elements::*; //Element,CommonAttributes,HasExtraAndChildren pub use self::extra_attributes::ExtraAttributes; #[cfg(test)] mod tests { use super::*; use std::default::Default; #[test] fn imperative() { let mut doc = Document::default(); let mut title = Title::default(); let url = "https://example.com/image.jpg".parse().unwrap(); let image = ImageInline::with_extra(extra_attributes::ImageInline::new(url)); title.append_child("Hi"); title.append_child(image); doc.append_child(title); println!("{:?}", doc); } #[test] fn descriptive() { let doc = Document::with_children(vec![Title::with_children(vec![ "Hi".into(), ImageInline::with_extra(extra_attributes::ImageInline::new( "https://example.com/image.jpg".parse().unwrap(), )) .into(), ]) .into()]); println!("{:?}", doc); } } document_tree-0.4.1/src/macro_util.rs000064400000000000000000000022711046102023000157600ustar 00000000000000macro_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))); } } document_tree-0.4.1/src/url.rs000064400000000000000000000043271046102023000144300ustar 00000000000000use std::fmt; use std::str::FromStr; use serde_derive::Serialize; use url::{self, ParseError}; 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 { Ok(url::Url::parse(input)?.into()) } pub fn parse_relative(input: &str) -> Result { // 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 for Url { fn from(url: url::Url) -> Self { Url(url.into()) } } 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 { Url::parse_absolute(input).or_else(|_| Url::parse_relative(input)) } }