References for images
This commit is contained in:
parent
c2cfd3f72c
commit
e5ba622e0d
9 changed files with 332 additions and 8 deletions
|
@ -1,6 +1,7 @@
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::compiler::compiler::Compiler;
|
use crate::compiler::compiler::Compiler;
|
||||||
|
use crate::elements::reference::Reference;
|
||||||
use crate::parser::source::Token;
|
use crate::parser::source::Token;
|
||||||
use downcast_rs::impl_downcast;
|
use downcast_rs::impl_downcast;
|
||||||
use downcast_rs::Downcast;
|
use downcast_rs::Downcast;
|
||||||
|
@ -68,6 +69,9 @@ pub trait ReferenceableElement: Element {
|
||||||
|
|
||||||
/// Key for refcounting
|
/// Key for refcounting
|
||||||
fn refcount_key(&self) -> &'static str;
|
fn refcount_key(&self) -> &'static str;
|
||||||
|
|
||||||
|
/// Creates the reference element
|
||||||
|
fn compile_reference(&self, compiler: &Compiler, document: &dyn Document, reference: &Reference, refid: usize) -> Result<String, String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::fmt::Debug for dyn ReferenceableElement {
|
impl core::fmt::Debug for dyn ReferenceableElement {
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
pub fn validate_refname(name: &str) -> Result<&str, String> {
|
use super::document::Document;
|
||||||
|
|
||||||
|
pub fn validate_refname<'a>(
|
||||||
|
document: &dyn Document,
|
||||||
|
name: &'a str,
|
||||||
|
check_duplicate: bool,
|
||||||
|
) -> Result<&'a str, String> {
|
||||||
let trimmed = name.trim_start().trim_end();
|
let trimmed = name.trim_start().trim_end();
|
||||||
if trimmed.is_empty() {
|
if trimmed.is_empty() {
|
||||||
return Err("Refname cannot be empty".to_string());
|
return Err("Refname cannot be empty".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
for c in trimmed.chars() {
|
for c in trimmed.chars() {
|
||||||
if c.is_ascii_punctuation() {
|
if c.is_ascii_punctuation() && !(c == '.' || c == '_') {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"Refname `{trimmed}` cannot contain punctuation codepoint: `{c}`"
|
"Refname `{trimmed}` cannot contain punctuation codepoint: `{c}`"
|
||||||
));
|
));
|
||||||
|
@ -24,5 +30,43 @@ pub fn validate_refname(name: &str) -> Result<&str, String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if check_duplicate && document.get_reference(trimmed).is_some() {
|
||||||
|
Err(format!("Refname `{trimmed}` is already in use!"))
|
||||||
|
} else {
|
||||||
Ok(trimmed)
|
Ok(trimmed)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::parser::langparser::LangParser;
|
||||||
|
use crate::parser::parser::Parser;
|
||||||
|
use crate::parser::source::SourceFile;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validate_refname_tests() {
|
||||||
|
let source = Rc::new(SourceFile::with_content(
|
||||||
|
"".to_string(),
|
||||||
|
"#{ref} Section".to_string(),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
let parser = LangParser::default();
|
||||||
|
let doc = parser.parse(source, None);
|
||||||
|
|
||||||
|
assert_eq!(validate_refname(&*doc, " abc ", true), Ok("abc"));
|
||||||
|
assert_eq!(
|
||||||
|
validate_refname(&*doc, " Some_reference ", true),
|
||||||
|
Ok("Some_reference")
|
||||||
|
);
|
||||||
|
assert!(validate_refname(&*doc, "", true).is_err());
|
||||||
|
assert!(validate_refname(&*doc, "\n", true).is_err());
|
||||||
|
assert!(validate_refname(&*doc, "'", true).is_err());
|
||||||
|
assert!(validate_refname(&*doc, "]", true).is_err());
|
||||||
|
|
||||||
|
// Duplicate
|
||||||
|
assert!(validate_refname(&*doc, "ref", true).is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ use crate::document::element::ElemKind;
|
||||||
use crate::document::element::Element;
|
use crate::document::element::Element;
|
||||||
use crate::document::element::ReferenceableElement;
|
use crate::document::element::ReferenceableElement;
|
||||||
use crate::document::references::validate_refname;
|
use crate::document::references::validate_refname;
|
||||||
|
use crate::parser::parser::Parser;
|
||||||
use crate::parser::parser::ReportColors;
|
use crate::parser::parser::ReportColors;
|
||||||
use crate::parser::rule::RegexRule;
|
use crate::parser::rule::RegexRule;
|
||||||
use crate::parser::source::Source;
|
use crate::parser::source::Source;
|
||||||
|
@ -34,6 +35,7 @@ use crate::parser::util::PropertyMapError;
|
||||||
use crate::parser::util::PropertyParser;
|
use crate::parser::util::PropertyParser;
|
||||||
|
|
||||||
use super::paragraph::Paragraph;
|
use super::paragraph::Paragraph;
|
||||||
|
use super::reference::Reference;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum MediaType {
|
pub enum MediaType {
|
||||||
|
@ -186,6 +188,32 @@ impl ReferenceableElement for Medium {
|
||||||
fn reference_name(&self) -> Option<&String> { Some(&self.reference) }
|
fn reference_name(&self) -> Option<&String> { Some(&self.reference) }
|
||||||
|
|
||||||
fn refcount_key(&self) -> &'static str { "medium" }
|
fn refcount_key(&self) -> &'static str { "medium" }
|
||||||
|
|
||||||
|
fn compile_reference(
|
||||||
|
&self,
|
||||||
|
compiler: &Compiler,
|
||||||
|
_document: &dyn Document,
|
||||||
|
reference: &Reference,
|
||||||
|
refid: usize,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
match compiler.target() {
|
||||||
|
Target::HTML => {
|
||||||
|
let caption = reference
|
||||||
|
.caption()
|
||||||
|
.map_or(format!("({refid})"), |cap| cap.clone());
|
||||||
|
|
||||||
|
// TODO Handle other kind of media
|
||||||
|
match self.media_type {
|
||||||
|
MediaType::IMAGE => Ok(format!(
|
||||||
|
r#"<a class="medium-ref">{caption}<img src="{}"></a>"#,
|
||||||
|
self.uri
|
||||||
|
)),
|
||||||
|
_ => todo!(""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => todo!(""),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MediaRule {
|
pub struct MediaRule {
|
||||||
|
@ -296,7 +324,7 @@ impl RegexRule for MediaRule {
|
||||||
fn on_regex_match<'a>(
|
fn on_regex_match<'a>(
|
||||||
&self,
|
&self,
|
||||||
_: usize,
|
_: usize,
|
||||||
parser: &dyn crate::parser::parser::Parser,
|
parser: &dyn Parser,
|
||||||
document: &'a (dyn Document<'a> + 'a),
|
document: &'a (dyn Document<'a> + 'a),
|
||||||
token: Token,
|
token: Token,
|
||||||
matches: Captures,
|
matches: Captures,
|
||||||
|
@ -305,7 +333,7 @@ impl RegexRule for MediaRule {
|
||||||
|
|
||||||
let refname = match (
|
let refname = match (
|
||||||
matches.get(1).unwrap(),
|
matches.get(1).unwrap(),
|
||||||
validate_refname(matches.get(1).unwrap().as_str()),
|
validate_refname(document, matches.get(1).unwrap().as_str(), true),
|
||||||
) {
|
) {
|
||||||
(_, Ok(refname)) => refname.to_string(),
|
(_, Ok(refname)) => refname.to_string(),
|
||||||
(m, Err(err)) => {
|
(m, Err(err)) => {
|
||||||
|
|
|
@ -14,3 +14,4 @@ pub mod tex;
|
||||||
pub mod graphviz;
|
pub mod graphviz;
|
||||||
pub mod raw;
|
pub mod raw;
|
||||||
pub mod media;
|
pub mod media;
|
||||||
|
pub mod reference;
|
||||||
|
|
211
src/elements/reference.rs
Normal file
211
src/elements/reference.rs
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ops::Range;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use ariadne::Fmt;
|
||||||
|
use ariadne::Label;
|
||||||
|
use ariadne::Report;
|
||||||
|
use ariadne::ReportKind;
|
||||||
|
use regex::Captures;
|
||||||
|
use regex::Match;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
use crate::compiler::compiler::Compiler;
|
||||||
|
use crate::compiler::compiler::Target;
|
||||||
|
use crate::document::document::Document;
|
||||||
|
use crate::document::element::ElemKind;
|
||||||
|
use crate::document::element::Element;
|
||||||
|
use crate::document::references::validate_refname;
|
||||||
|
use crate::parser::parser::Parser;
|
||||||
|
use crate::parser::parser::ReportColors;
|
||||||
|
use crate::parser::rule::RegexRule;
|
||||||
|
use crate::parser::source::Source;
|
||||||
|
use crate::parser::source::Token;
|
||||||
|
use crate::parser::util;
|
||||||
|
use crate::parser::util::Property;
|
||||||
|
use crate::parser::util::PropertyMap;
|
||||||
|
use crate::parser::util::PropertyParser;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Reference {
|
||||||
|
pub(self) location: Token,
|
||||||
|
pub(self) refname: String,
|
||||||
|
pub(self) caption: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Reference {
|
||||||
|
pub fn caption(&self) -> Option<&String> { self.caption.as_ref() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Element for Reference {
|
||||||
|
fn location(&self) -> &Token { &self.location }
|
||||||
|
|
||||||
|
fn kind(&self) -> ElemKind { ElemKind::Inline }
|
||||||
|
|
||||||
|
fn element_name(&self) -> &'static str { "Reference" }
|
||||||
|
|
||||||
|
fn to_string(&self) -> String { format!("{self:#?}") }
|
||||||
|
|
||||||
|
fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result<String, String> {
|
||||||
|
match compiler.target() {
|
||||||
|
Target::HTML => {
|
||||||
|
let elemref = document.get_reference(self.refname.as_str()).unwrap();
|
||||||
|
let elem = document.get_from_reference(&elemref).unwrap();
|
||||||
|
|
||||||
|
elem.compile_reference(
|
||||||
|
compiler,
|
||||||
|
document,
|
||||||
|
self,
|
||||||
|
compiler.reference_id(document, elemref),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => todo!(""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ReferenceRule {
|
||||||
|
re: [Regex; 1],
|
||||||
|
properties: PropertyParser,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReferenceRule {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut props = HashMap::new();
|
||||||
|
props.insert(
|
||||||
|
"caption".to_string(),
|
||||||
|
Property::new(
|
||||||
|
false,
|
||||||
|
"Override the display of the reference".to_string(),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Self {
|
||||||
|
re: [Regex::new(r"§\{(.*)\}(\[((?:\\.|[^\\\\])*?)\])?").unwrap()],
|
||||||
|
properties: PropertyParser::new(props),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_properties(
|
||||||
|
&self,
|
||||||
|
colors: &ReportColors,
|
||||||
|
token: &Token,
|
||||||
|
m: &Option<Match>,
|
||||||
|
) -> Result<PropertyMap, Report<'_, (Rc<dyn Source>, Range<usize>)>> {
|
||||||
|
match m {
|
||||||
|
None => match self.properties.default() {
|
||||||
|
Ok(properties) => Ok(properties),
|
||||||
|
Err(e) => Err(
|
||||||
|
Report::build(ReportKind::Error, token.source(), token.start())
|
||||||
|
.with_message("Invalid Media Properties")
|
||||||
|
.with_label(
|
||||||
|
Label::new((token.source().clone(), token.range.clone()))
|
||||||
|
.with_message(format!("Media is missing required property: {e}"))
|
||||||
|
.with_color(colors.error),
|
||||||
|
)
|
||||||
|
.finish(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Some(props) => {
|
||||||
|
let processed =
|
||||||
|
util::process_escaped('\\', "]", props.as_str().trim_start().trim_end());
|
||||||
|
match self.properties.parse(processed.as_str()) {
|
||||||
|
Err(e) => Err(
|
||||||
|
Report::build(ReportKind::Error, token.source(), props.start())
|
||||||
|
.with_message("Invalid Media Properties")
|
||||||
|
.with_label(
|
||||||
|
Label::new((token.source().clone(), props.range()))
|
||||||
|
.with_message(e)
|
||||||
|
.with_color(colors.error),
|
||||||
|
)
|
||||||
|
.finish(),
|
||||||
|
),
|
||||||
|
Ok(properties) => Ok(properties),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RegexRule for ReferenceRule {
|
||||||
|
fn name(&self) -> &'static str { "Reference" }
|
||||||
|
|
||||||
|
fn regexes(&self) -> &[regex::Regex] { &self.re }
|
||||||
|
|
||||||
|
fn on_regex_match<'a>(
|
||||||
|
&self,
|
||||||
|
_: usize,
|
||||||
|
parser: &dyn Parser,
|
||||||
|
document: &'a (dyn Document<'a> + 'a),
|
||||||
|
token: Token,
|
||||||
|
matches: Captures,
|
||||||
|
) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
|
||||||
|
let mut reports = vec![];
|
||||||
|
|
||||||
|
let refname = match (
|
||||||
|
matches.get(1).unwrap(),
|
||||||
|
validate_refname(document, matches.get(1).unwrap().as_str(), false),
|
||||||
|
) {
|
||||||
|
(m, Ok(refname)) => {
|
||||||
|
if document.get_reference(refname).is_none() {
|
||||||
|
reports.push(
|
||||||
|
Report::build(ReportKind::Error, token.source(), m.start())
|
||||||
|
.with_message("Uknown Reference Refname")
|
||||||
|
.with_label(
|
||||||
|
Label::new((token.source().clone(), m.range())).with_message(
|
||||||
|
format!(
|
||||||
|
"Could not find element with reference: `{}`",
|
||||||
|
refname.fg(parser.colors().info)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.finish(),
|
||||||
|
);
|
||||||
|
return reports;
|
||||||
|
}
|
||||||
|
refname.to_string()
|
||||||
|
}
|
||||||
|
(m, Err(err)) => {
|
||||||
|
reports.push(
|
||||||
|
Report::build(ReportKind::Error, token.source(), m.start())
|
||||||
|
.with_message("Invalid Reference Refname")
|
||||||
|
.with_label(
|
||||||
|
Label::new((token.source().clone(), m.range())).with_message(err),
|
||||||
|
)
|
||||||
|
.finish(),
|
||||||
|
);
|
||||||
|
return reports;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Properties
|
||||||
|
let properties = match self.parse_properties(parser.colors(), &token, &matches.get(3)) {
|
||||||
|
Ok(pm) => pm,
|
||||||
|
Err(report) => {
|
||||||
|
reports.push(report);
|
||||||
|
return reports;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let caption = properties
|
||||||
|
.get("caption", |_, value| -> Result<String, ()> {
|
||||||
|
Ok(value.clone())
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
.and_then(|(_, s)| Some(s));
|
||||||
|
|
||||||
|
parser.push(
|
||||||
|
document,
|
||||||
|
Box::new(Reference {
|
||||||
|
location: token,
|
||||||
|
refname,
|
||||||
|
caption,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
reports
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lua_bindings<'lua>(&self, _lua: &'lua mlua::Lua) -> Vec<(String, mlua::Function<'lua>)> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ use super::tex::TexRule;
|
||||||
use super::text::TextRule;
|
use super::text::TextRule;
|
||||||
use super::variable::VariableRule;
|
use super::variable::VariableRule;
|
||||||
use super::variable::VariableSubstitutionRule;
|
use super::variable::VariableSubstitutionRule;
|
||||||
|
use super::reference::ReferenceRule;
|
||||||
|
|
||||||
pub fn register<P: Parser>(parser: &mut P) {
|
pub fn register<P: Parser>(parser: &mut P) {
|
||||||
parser.add_rule(Box::new(CommentRule::new()), None).unwrap();
|
parser.add_rule(Box::new(CommentRule::new()), None).unwrap();
|
||||||
|
@ -35,4 +36,5 @@ pub fn register<P: Parser>(parser: &mut P) {
|
||||||
parser.add_rule(Box::new(SectionRule::new()), None).unwrap();
|
parser.add_rule(Box::new(SectionRule::new()), None).unwrap();
|
||||||
parser.add_rule(Box::new(LinkRule::new()), None).unwrap();
|
parser.add_rule(Box::new(LinkRule::new()), None).unwrap();
|
||||||
parser.add_rule(Box::new(TextRule::default()), None).unwrap();
|
parser.add_rule(Box::new(TextRule::default()), None).unwrap();
|
||||||
|
parser.add_rule(Box::new(ReferenceRule::new()), None).unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,16 @@ impl ReferenceableElement for Section {
|
||||||
fn reference_name(&self) -> Option<&String> { self.reference.as_ref() }
|
fn reference_name(&self) -> Option<&String> { self.reference.as_ref() }
|
||||||
|
|
||||||
fn refcount_key(&self) -> &'static str { "section" }
|
fn refcount_key(&self) -> &'static str { "section" }
|
||||||
|
|
||||||
|
fn compile_reference(
|
||||||
|
&self,
|
||||||
|
compiler: &Compiler,
|
||||||
|
document: &dyn Document,
|
||||||
|
reference: &super::reference::Reference,
|
||||||
|
refid: usize,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SectionRule {
|
pub struct SectionRule {
|
||||||
|
|
|
@ -85,8 +85,7 @@ fn main() {
|
||||||
println!("-- END AST DEBUGGING --");
|
println!("-- END AST DEBUGGING --");
|
||||||
}
|
}
|
||||||
|
|
||||||
if debug_opts.contains(&"ref".to_string())
|
if debug_opts.contains(&"ref".to_string()) {
|
||||||
{
|
|
||||||
println!("-- BEGIN REFERENCES DEBUGGING --");
|
println!("-- BEGIN REFERENCES DEBUGGING --");
|
||||||
let sc = doc.scope().borrow();
|
let sc = doc.scope().borrow();
|
||||||
sc.referenceable.iter().for_each(|(name, reference)| {
|
sc.referenceable.iter().for_each(|(name, reference)| {
|
||||||
|
|
27
style.css
27
style.css
|
@ -97,7 +97,7 @@ div.medium p.medium-refname {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #9424af;
|
color: #d367c1;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.medium p {
|
div.medium p {
|
||||||
|
@ -108,3 +108,28 @@ div.medium p {
|
||||||
|
|
||||||
text-align: justify;
|
text-align: justify;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.medium-ref {
|
||||||
|
display: inline;
|
||||||
|
|
||||||
|
font-weight: bold;
|
||||||
|
color: #d367c1;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.medium-ref:hover {
|
||||||
|
background: #334;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.medium-ref img {
|
||||||
|
display: none;
|
||||||
|
margin: 1.3em 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover.medium-ref img {
|
||||||
|
max-width: 50%;
|
||||||
|
margin: auto;
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
box-shadow: 0px 0px 6px 2px rgba(0,0,0,0.75);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue