diff options
| author | Philipp A | 2018-11-27 00:45:22 +0100 | 
|---|---|---|
| committer | Philipp A | 2018-11-27 00:45:22 +0100 | 
| commit | 71d26fbdc4825b4c2a2db125dde388dc5c5a196c (patch) | |
| tree | 15e485c6324bf2a5e6997a0a766345d44d0c4d50 | |
| parent | 70667b529fcf24815819636b9a105d17ae38e796 (diff) | |
| download | rust-rst-71d26fbdc4825b4c2a2db125dde388dc5c5a196c.tar.bz2 | |
Support inline Images
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | src/document_tree.rs | 1 | ||||
| -rw-r--r-- | src/document_tree/attribute_types.rs | 68 | ||||
| -rw-r--r-- | src/document_tree/element_categories.rs | 8 | ||||
| -rw-r--r-- | src/document_tree/elements.rs | 15 | ||||
| -rw-r--r-- | src/document_tree/extra_attributes.rs | 92 | ||||
| -rw-r--r-- | src/document_tree/serde_util.rs | 12 | ||||
| -rw-r--r-- | src/parser/conversion.rs | 57 | ||||
| -rw-r--r-- | src/parser/tests.rs | 3 | 
9 files changed, 194 insertions, 63 deletions
| @@ -24,6 +24,7 @@ path = 'src/bin.rs'  failure = '0.1.3'  failure_derive = '0.1.3'  url = '1.7.2' +regex = '1.0.6'  bitflags = '1.0.4'  unicode_categories = '0.1.1'  pest        = { git = 'https://github.com/pest-parser/pest' } 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<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 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 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 @@ -117,7 +117,13 @@ mod test {  	}  	#[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<S>(url_opt: &Option<Url>, serializer: S) -> Result<S::Ok, S::Error> 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<extra_attributes::$name> 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<A> { -//	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<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 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<bool>, rtrim: Option<bool> });  impl_extra!(Comment { space: FixedSpace }); -impl_extra!(Target { refuri: Option<Url>, refid: Option<ID>, refname: Vec<NameToken>, anonymous: bool }); +impl_extra!(Target { +	#[serde(serialize_with = "serialize_opt_url")] +	refuri: Option<Url>, +	refid: Option<ID>, +	refname: Vec<NameToken>, +	anonymous: bool, +});  impl_extra!(Raw { space: FixedSpace, format: Vec<NameToken> }); -impl_extra!(#[derive(Debug)] Image { -	align: Option<AlignHV>, +impl_extra!(#[derive(Debug,Serialize)] Image { +	#[serde(serialize_with = "serialize_url")]  	uri: Url, +	align: Option<AlignHV>,  	alt: Option<String>,  	height: Option<Measure>,  	width: Option<Measure>, -	scale: Option<f64>, +	scale: Option<u8>, +	#[serde(serialize_with = "serialize_opt_url")] +	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 @@ -78,20 +64,46 @@ impl_extra!(Table {}); //TODO  impl_extra!(OptionArgument { delimiter: Option<String> }); -impl_extra!(Reference { name: Option<String>, refuri: Option<Url>, refid: Option<ID>, refname: Vec<NameToken> }); +impl_extra!(Reference { +	name: Option<String>, +	#[serde(serialize_with = "serialize_opt_url")] +	refuri: Option<Url>, +	refid: Option<ID>, +	refname: Vec<NameToken>, +});  impl_extra!(FootnoteReference { refid: Option<ID>, refname: Vec<NameToken>, auto: Option<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 { refuri: Option<Url>, refid: Option<ID>, refname: Vec<NameToken>, anonymous: bool }); +impl_extra!(TargetInline { +	#[serde(serialize_with = "serialize_opt_url")] +	refuri: Option<Url>, +	refid: Option<ID>, +	refname: Vec<NameToken>, +	anonymous: bool, +});  impl_extra!(RawInline { space: FixedSpace, format: Vec<NameToken> }); -impl_extra!(#[derive(Debug)] ImageInline { -	align: Option<AlignHV>, +impl_extra!(#[derive(Debug,Serialize)] ImageInline { +	#[serde(serialize_with = "serialize_url")]  	uri: Url, +	align: Option<AlignHV>,  	alt: Option<String>,  	height: Option<Measure>,  	width: Option<Measure>, -	scale: Option<f64>, +	scale: Option<u8>, +	#[serde(serialize_with = "serialize_opt_url")] +	target: Option<Url>,  // 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<S>(url: &Url, serializer: S) -> Result<S::Ok, S::Error> where S: serde::ser::Serializer { +	serializer.serialize_str(url.as_str()) +} + +pub fn serialize_opt_url<S>(url_opt: &Option<Url>, serializer: S) -> Result<S::Ok, S::Error> 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<Rule>) -> Result<e::Document, Error> {  fn convert_ssubel(pair: Pair<Rule>) -> Result<c::StructuralSubElement, Error> {      // 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<Rule>) -> e::Title {  }  fn convert_target(pair: Pair<Rule>) -> Result<e::Target, Error> { -    let mut attrs = extra_attributes::Target { +    let mut attrs = a::Target {          anonymous: false,          ..Default::default()      }; @@ -72,6 +73,46 @@ fn convert_target(pair: Pair<Rule>) -> Result<e::Target, Error> {      Ok(e::Target::new(Default::default(), attrs))  } +fn convert_substitution_def(pair: Pair<Rule>) -> Result<e::SubstitutionDefinition, Error> { +    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<Rule>) -> Result<e::ImageInline, Error> { +    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<Rule>) -> Result<c::BodyElement, Error> {      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! { | 
