From 71d26fbdc4825b4c2a2db125dde388dc5c5a196c Mon Sep 17 00:00:00 2001 From: Philipp A Date: Tue, 27 Nov 2018 00:45:22 +0100 Subject: Support inline Images --- src/document_tree.rs | 1 + src/document_tree/attribute_types.rs | 68 ++++++++++++++++++++++-- src/document_tree/element_categories.rs | 8 ++- src/document_tree/elements.rs | 15 ++---- src/document_tree/extra_attributes.rs | 92 +++++++++++++++++++-------------- src/document_tree/serde_util.rs | 12 +++++ src/parser/conversion.rs | 57 +++++++++++++++++--- src/parser/tests.rs | 3 ++ 8 files changed, 193 insertions(+), 63 deletions(-) create mode 100644 src/document_tree/serde_util.rs (limited to 'src') diff --git a/src/document_tree.rs b/src/document_tree.rs index 3d983da..749654c 100644 --- a/src/document_tree.rs +++ b/src/document_tree.rs @@ -3,6 +3,7 @@ #[macro_use] mod macro_util; +mod serde_util; pub mod elements; pub mod element_categories; diff --git a/src/document_tree/attribute_types.rs b/src/document_tree/attribute_types.rs index 9ad66be..cd766f2 100644 --- a/src/document_tree/attribute_types.rs +++ b/src/document_tree/attribute_types.rs @@ -1,4 +1,8 @@ +use std::str::FromStr; + +use failure::{Error,bail,format_err}; use serde_derive::Serialize; +use regex::Regex; #[derive(Debug,Serialize)] pub enum EnumeratedListType { @@ -20,8 +24,64 @@ impl Default for FixedSpace { fn default() -> FixedSpace { FixedSpace::Preserve #[derive(Debug,Serialize)] pub struct NameToken(pub String); #[derive(Debug,Serialize)] -pub enum Measure { - Pixel(usize), - Em(usize), - //TODO +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 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 test { + use super::*; + + fn test_parse_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(); + } + + // TODO: Test parsing measures } diff --git a/src/document_tree/element_categories.rs b/src/document_tree/element_categories.rs index df291cf..b4d63a2 100644 --- a/src/document_tree/element_categories.rs +++ b/src/document_tree/element_categories.rs @@ -116,8 +116,14 @@ mod test { let _: SubStructure = Paragraph::default().into(); } + #[test] + fn test_convert_even_more() { + let _: StructuralSubElement = Paragraph::default().into(); + } + #[test] fn test_convert_super() { - let _: StructuralSubElement = BodyElement::Paragraph(Paragraph::default()).into(); + let be: BodyElement = Paragraph::default().into(); + let _: StructuralSubElement = be.into(); } } diff --git a/src/document_tree/elements.rs b/src/document_tree/elements.rs index 99772b2..2b99d97 100644 --- a/src/document_tree/elements.rs +++ b/src/document_tree/elements.rs @@ -1,15 +1,10 @@ use serde_derive::Serialize; use url::Url; +use super::serde_util::serialize_opt_url; use super::extra_attributes::{self,ExtraAttributes}; use super::element_categories::*; -fn serialize_opt_url(url_opt: &Option, serializer: S) -> Result where S: serde::ser::Serializer { - match url_opt { - Some(ref url) => serializer.serialize_some(url.as_str()), - None => serializer.serialize_none(), - } -} //-----------------\\ //Element hierarchy\\ @@ -61,9 +56,9 @@ macro_rules! impl_children { ($name:ident, $childtype:ident) => ( } )} -macro_rules! impl_extra { ($name:ident) => ( +macro_rules! impl_extra { ($name:ident $($more:tt)*) => ( impl ExtraAttributes for $name { -// fn with_extra(extra: extra_attributes::$name) -> $name { $name { extra: extra, ..Default::default() } } + 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 } } @@ -98,7 +93,7 @@ macro_rules! impl_elem { #[serde(flatten)] common: CommonAttributes, #[serde(flatten)] extra: extra_attributes::$name, }); - impl_element!($name); impl_extra!($name); + impl_element!($name); impl_extra!($name, ..Default::default()); }; ($name:ident; *) => { //same as above with no default impl_new!(pub struct $name { @@ -120,7 +115,7 @@ macro_rules! impl_elem { #[serde(flatten)] extra: extra_attributes::$name, children: Vec<$childtype>, }); - impl_element!($name); impl_extra!($name); impl_children!($name, $childtype); + impl_element!($name); impl_extra!($name, ..Default::default()); impl_children!($name, $childtype); }; } diff --git a/src/document_tree/extra_attributes.rs b/src/document_tree/extra_attributes.rs index 80fe045..916ba41 100644 --- a/src/document_tree/extra_attributes.rs +++ b/src/document_tree/extra_attributes.rs @@ -1,50 +1,27 @@ use url::Url; -use serde::{ - Serialize, - Serializer, - ser::SerializeStruct, -}; +use serde_derive::Serialize; +use super::serde_util::{serialize_url,serialize_opt_url}; use super::attribute_types::{FixedSpace,ID,NameToken,AlignHV,AlignH,Measure,EnumeratedListType}; pub trait ExtraAttributes { -// fn with_extra(extra: A) -> Self; + fn with_extra(extra: A) -> Self; fn extra (& self) -> & A; fn extra_mut(&mut self) -> &mut A; } -macro_rules! count { - () => (0usize); - ( $x:tt $($xs:tt)* ) => (1usize + count!($($xs)*)); -} - -macro_rules! ser_url { - ($self:ident, refuri ) => { $self.refuri.as_ref().map(|uri| uri.to_string()) }; - ($self:ident, uri ) => { $self.uri.to_string() }; - ($self:ident, $param:ident) => { $self.$param }; -} - macro_rules! impl_extra { - ( $name:ident { $( $param:ident : $type:ty ),* $(,)* } ) => ( + ( $name:ident { $( $(#[$pattr:meta])* $param:ident : $type:ty ),* $(,)* } ) => ( impl_extra!( - #[derive(Default,Debug)] - $name { $( $param : $type, )* } + #[derive(Default,Debug,Serialize)] + $name { $( $(#[$pattr])* $param : $type, )* } ); ); - ( $(#[$attr:meta])+ $name:ident { $( $param:ident : $type:ty ),* $(,)* } ) => ( + ( $(#[$attr:meta])+ $name:ident { $( $(#[$pattr:meta])* $param:ident : $type:ty ),* $(,)* } ) => ( $(#[$attr])+ pub struct $name { - $( pub $param : $type, )* - } - - impl Serialize for $name { - fn serialize(&self, serializer: S) -> Result where S: Serializer { - #[allow(unused_mut)] - let mut state = serializer.serialize_struct(stringify!($name), count!($($param)*))?; - $( state.serialize_field(stringify!($param), &ser_url!(self, $param))?; )* - state.end() - } + $( $(#[$pattr])* pub $param : $type, )* } ); } @@ -54,15 +31,24 @@ impl_extra!(LiteralBlock { space: FixedSpace }); impl_extra!(DoctestBlock { space: FixedSpace }); impl_extra!(SubstitutionDefinition { ltrim: Option, rtrim: Option }); impl_extra!(Comment { space: FixedSpace }); -impl_extra!(Target { refuri: Option, refid: Option, refname: Vec, anonymous: bool }); +impl_extra!(Target { + #[serde(serialize_with = "serialize_opt_url")] + refuri: Option, + refid: Option, + refname: Vec, + anonymous: bool, +}); impl_extra!(Raw { space: FixedSpace, format: Vec }); -impl_extra!(#[derive(Debug)] Image { - align: Option, +impl_extra!(#[derive(Debug,Serialize)] Image { + #[serde(serialize_with = "serialize_url")] uri: Url, + align: Option, alt: Option, height: Option, width: Option, - scale: Option, + scale: Option, + #[serde(serialize_with = "serialize_opt_url")] + 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 @@ -78,20 +64,46 @@ impl_extra!(Table {}); //TODO impl_extra!(OptionArgument { delimiter: Option }); -impl_extra!(Reference { name: Option, refuri: Option, refid: Option, refname: Vec }); +impl_extra!(Reference { + name: Option, + #[serde(serialize_with = "serialize_opt_url")] + refuri: Option, + refid: Option, + refname: Vec, +}); impl_extra!(FootnoteReference { refid: Option, refname: Vec, auto: Option }); 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 { refuri: Option, refid: Option, refname: Vec, anonymous: bool }); +impl_extra!(TargetInline { + #[serde(serialize_with = "serialize_opt_url")] + refuri: Option, + refid: Option, + refname: Vec, + anonymous: bool, +}); impl_extra!(RawInline { space: FixedSpace, format: Vec }); -impl_extra!(#[derive(Debug)] ImageInline { - align: Option, +impl_extra!(#[derive(Debug,Serialize)] ImageInline { + #[serde(serialize_with = "serialize_url")] uri: Url, + align: Option, alt: Option, height: Option, width: Option, - scale: Option, + scale: Option, + #[serde(serialize_with = "serialize_opt_url")] + target: Option, // Not part of the DTD but a valid argument }); +impl ImageInline { + pub fn new(uri: Url) -> ImageInline { ImageInline { + uri: uri, + align: None, + alt: None, + height: None, + width: None, + scale: None, + target: None, + } } +} diff --git a/src/document_tree/serde_util.rs b/src/document_tree/serde_util.rs new file mode 100644 index 0000000..2e24309 --- /dev/null +++ b/src/document_tree/serde_util.rs @@ -0,0 +1,12 @@ +use url::Url; + +pub fn serialize_url(url: &Url, serializer: S) -> Result where S: serde::ser::Serializer { + serializer.serialize_str(url.as_str()) +} + +pub fn serialize_opt_url(url_opt: &Option, serializer: S) -> Result where S: serde::ser::Serializer { + match url_opt { + Some(ref url) => serializer.serialize_some(url.as_str()), + None => serializer.serialize_none(), + } +} diff --git a/src/parser/conversion.rs b/src/parser/conversion.rs index 22a6ebe..e59098e 100644 --- a/src/parser/conversion.rs +++ b/src/parser/conversion.rs @@ -1,14 +1,14 @@ use url::Url; -use failure::Error; +use failure::{Error,bail}; use failure_derive::Fail; use pest::iterators::{Pairs,Pair}; use crate::document_tree::{ - HasChildren, + Element,HasChildren,ExtraAttributes, elements as e, element_categories as c, attribute_types::ID, - extra_attributes, + extra_attributes as a, }; use super::pest_rst::Rule; @@ -31,10 +31,11 @@ pub fn convert_document(pairs: Pairs) -> Result { fn convert_ssubel(pair: Pair) -> Result { // TODO: This is just a proof of concep. Keep closely to DTD in final version! match pair.as_rule() { - Rule::title => Ok(convert_title(pair).into()), - Rule::paragraph => Ok(e::Paragraph::with_children(vec![pair.as_str().into()]).into()), - Rule::target => Ok(convert_target(pair)?.into()), - Rule::admonition_gen => Ok(convert_admonition_gen(pair)?.into()), + Rule::title => Ok(convert_title(pair).into()), + Rule::paragraph => Ok(e::Paragraph::with_children(vec![pair.as_str().into()]).into()), + Rule::target => Ok(convert_target(pair)?.into()), + Rule::substitution_def => Ok(convert_substitution_def(pair)?.into()), + Rule::admonition_gen => Ok(convert_admonition_gen(pair)?.into()), rule => Err(ConversionError::UnknownRuleError { rule }.into()), } } @@ -57,7 +58,7 @@ fn convert_title(pair: Pair) -> e::Title { } fn convert_target(pair: Pair) -> Result { - let mut attrs = extra_attributes::Target { + let mut attrs = a::Target { anonymous: false, ..Default::default() }; @@ -72,6 +73,46 @@ fn convert_target(pair: Pair) -> Result { Ok(e::Target::new(Default::default(), attrs)) } +fn convert_substitution_def(pair: Pair) -> Result { + let mut pairs = pair.into_inner(); + let name = pairs.next().unwrap().as_str(); // Rule::substitution_name + let inner_pair = pairs.next().unwrap(); + let inner: c::TextOrInlineElement = match inner_pair.as_rule() { + Rule::image => convert_image_inline(inner_pair)?.into(), + rule => panic!("Unknown substitution rule {:?}", rule), + }; + let mut subst_def = e::SubstitutionDefinition::with_children(vec![inner.into()]); + subst_def.names_mut().push(name.to_owned()); + Ok(subst_def) +} + +fn convert_image_inline(pair: Pair) -> Result { + let mut pairs = pair.into_inner(); + let mut image = e::ImageInline::with_extra(a::ImageInline::new( + pairs.next().unwrap().as_str().parse()?, // line + )); + if let Some(opt_block) = pairs.next() { // image_opt_block + let options = opt_block.into_inner(); + for opt in options { + let mut opt_iter = opt.into_inner(); + let opt_name = opt_iter.next().unwrap(); + let opt_val = opt_iter.next().unwrap().as_str(); + match opt_name.as_str() { + "class" => image.classes_mut().push(opt_val.to_owned()), + "name" => image.names_mut().push(opt_val.to_owned()), + "alt" => image.extra_mut().alt = Some(opt_val.to_owned()), + "height" => image.extra_mut().height = Some(opt_val.parse()?), + "width" => image.extra_mut().width = Some(opt_val.parse()?), + "scale" => image.extra_mut().scale = Some(opt_val.parse()?), // TODO: can end with % + "align" => image.extra_mut().align = Some(opt_val.parse()?), + "target" => image.extra_mut().target = Some(opt_val.parse()?), + name => bail!("Unknown Image option {}", name), + } + } + } + Ok(image) +} + fn convert_admonition_gen(pair: Pair) -> Result { let mut iter = pair.into_inner(); let typ = iter.next().unwrap().as_str(); diff --git a/src/parser/tests.rs b/src/parser/tests.rs index 74d741d..7c5c114 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -106,6 +106,9 @@ fn admonitions() { }; } +// TODO: substitutions +// TODO: images + #[test] fn nested_lists() { parses_to! { -- cgit v1.2.3