summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Silverstone <dsilvers+gitlab@digital-scurf.org>2023-10-10 07:08:21 +0000
committerDaniel Silverstone <dsilvers+gitlab@digital-scurf.org>2023-10-10 07:08:21 +0000
commit58350fed3216b3ec2875503f6036e1a7f982088e (patch)
tree9fb29d0b107d5e1edb78954e68e35270b5fc391c
parent478147c59de2547cb1e3e0a0cd5309c25f4453dc (diff)
parent76838e218f8c0943de43befce434fa8845a68e86 (diff)
downloadsubplot-58350fed3216b3ec2875503f6036e1a7f982088e.tar.gz
Merge branch 'liw/html-correctness' into 'main'
Various HTML output fixes Closes #336 See merge request subplot/subplot!355
-rwxr-xr-xcheck5
-rw-r--r--src/html.rs63
-rw-r--r--src/md.rs23
3 files changed, 73 insertions, 18 deletions
diff --git a/check b/check
index 58407aa..d1edc07 100755
--- a/check
+++ b/check
@@ -320,7 +320,9 @@ def check_subplots(r):
base = os.path.basename(subplot)
base, _ = os.path.splitext(subplot)
base = os.path.join(output, base)
- r.docgen(subplot, doc_template, base + ".html", cwd=dirname)
+ html = base + ".html"
+ r.docgen(subplot, doc_template, html, cwd=dirname)
+ r.runcmd(["tidy", "-errors", html], cwd=dirname)
def tail(filename, numlines=100):
@@ -344,6 +346,7 @@ def check_tooling(r):
"plantuml",
"rustc",
"rustfmt",
+ "tidy",
]
for command in commands:
if not r.got_command(command):
diff --git a/src/html.rs b/src/html.rs
index f863727..1e0dafa 100644
--- a/src/html.rs
+++ b/src/html.rs
@@ -7,10 +7,13 @@ use line_col::LineColLookup;
use log::{debug, trace};
use pulldown_cmark::{CodeBlockKind, Event, HeadingLevel, Options, Parser, Tag};
use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
use std::fmt::Write as _;
use std::io::Write;
use std::path::{Path, PathBuf};
+const DOCTYPE: &str = "<!DOCTYPE html>";
+
/// A HTML page, consisting of a head and a body.
#[derive(Debug)]
pub struct HtmlPage {
@@ -47,8 +50,11 @@ impl HtmlPage {
pub fn serialize(&self) -> Result<String, HtmlError> {
let mut html = Element::new(ElementTag::Html);
html.push_child(Content::Elt(self.head.clone()));
- html.push_child(Content::Elt(self.body.clone()));
- html.serialize()
+ let mut body = Element::new(ElementTag::Body);
+ body.push_child(Content::Elt(self.body.clone()));
+ html.push_child(Content::Elt(body));
+ let html = html.serialize()?;
+ Ok(format!("{}\n{}", DOCTYPE, html))
}
/// Try to write an HTML page as text into a file.
@@ -82,7 +88,7 @@ pub fn parse(filename: &Path, markdown: &str) -> Result<Element, HtmlError> {
let p = Parser::new_ext(markdown, options).into_offset_iter();
let linecol = LineColLookup::new(markdown);
let mut stack = Stack::new();
- stack.push(Element::new(ElementTag::Body));
+ stack.push(Element::new(ElementTag::Div));
for (event, loc) in p {
trace!("event {:?}", event);
let (line, col) = linecol.get(loc.start);
@@ -150,6 +156,7 @@ pub fn parse(filename: &Path, markdown: &str) -> Result<Element, HtmlError> {
Tag::Image(_, url, title) => {
let mut e = Element::new(ElementTag::Img);
e.push_attribute(Attribute::new("src", url.as_ref()));
+ e.push_attribute(Attribute::new("alt", title.as_ref()));
if !title.is_empty() {
e.push_attribute(Attribute::new("title", title.as_ref()));
}
@@ -275,6 +282,13 @@ impl Element {
self.attrs.push(attr);
}
+ /// Drop all attributes with a given name.
+ pub fn drop_attributes(&mut self, unwanted: &[&str]) {
+ for uw in unwanted {
+ self.attrs.retain(|a| a.name() != *uw);
+ }
+ }
+
/// Append a new child to the element.
pub fn push_child(&mut self, child: Content) {
self.children.push(child);
@@ -321,9 +335,11 @@ impl Element {
fn fix_up_img_alt(&mut self) {
if self.tag == ElementTag::Img {
- let alt = as_plain_text(self.children());
- self.push_attribute(Attribute::new("alt", &alt));
- self.children.clear();
+ if !self.attrs.iter().any(|a| a.name() == "alt") {
+ let alt = as_plain_text(self.children());
+ self.push_attribute(Attribute::new("alt", &alt));
+ self.children.clear();
+ }
} else {
for child in self.children.iter_mut() {
if let Content::Elt(kid) = child {
@@ -376,9 +392,14 @@ impl Element {
}
fn serialize_attrs_to_buf(&self, buf: &mut String) -> Result<(), std::fmt::Error> {
+ let mut attrs = Attributes::default();
for attr in self.attrs.iter() {
- write!(buf, " {}", attr.name())?;
- if let Some(value) = attr.value() {
+ attrs.push(attr);
+ }
+
+ for (name, value) in attrs.iter() {
+ write!(buf, " {}", name)?;
+ if !value.is_empty() {
write!(buf, "=\"{}\"", encode_double_quoted_attribute(value))?;
}
}
@@ -487,6 +508,32 @@ impl ElementTag {
}
}
+#[derive(Debug, Default, Clone)]
+struct Attributes {
+ attrs: HashMap<String, String>,
+}
+
+impl Attributes {
+ fn push(&mut self, attr: &Attribute) {
+ if let Some(new_value) = attr.value() {
+ if let Some(old_value) = self.attrs.get_mut(attr.name()) {
+ assert!(!old_value.is_empty());
+ old_value.push(' ');
+ old_value.push_str(new_value);
+ } else {
+ self.attrs.insert(attr.name().into(), new_value.into());
+ }
+ } else {
+ assert!(!self.attrs.contains_key(attr.name()));
+ self.attrs.insert(attr.name().into(), "".into());
+ }
+ }
+
+ fn iter(&self) -> impl Iterator<Item = (&String, &String)> {
+ self.attrs.iter()
+ }
+}
+
/// An attribute of an HTML element.
#[derive(Clone, Debug)]
pub struct Attribute {
diff --git a/src/md.rs b/src/md.rs
index 554cc00..8573167 100644
--- a/src/md.rs
+++ b/src/md.rs
@@ -324,6 +324,7 @@ fn extract_scenario(e: &[StructureElement]) -> Result<(Option<Scenario>, usize),
}
mod typeset {
+ const UNWANTED_ATTRS: &[&str] = &["add-newline"];
use crate::html::{Attribute, Content, Element, ElementTag};
// use crate::parser::parse_scenario_snippet;
@@ -338,7 +339,7 @@ mod typeset {
use base64::prelude::{Engine as _, BASE64_STANDARD};
pub(crate) fn typeset_element(e: &Element) -> Result<Element, SubplotError> {
- match e.tag() {
+ let new = match e.tag() {
ElementTag::Pre if e.has_attr("class", "scenario") => typeset_scenario(e),
ElementTag::Pre if e.has_attr("class", "file") => typeset_file(e),
ElementTag::Pre if e.has_attr("class", "example") => typeset_example(e),
@@ -360,7 +361,10 @@ mod typeset {
}
Ok(new)
}
- }
+ };
+ let mut new = new?;
+ new.drop_attributes(UNWANTED_ATTRS);
+ Ok(new)
}
fn typeset_scenario(e: &Element) -> Result<Element, SubplotError> {
@@ -378,19 +382,19 @@ mod typeset {
fn typeset_dot(e: &Element) -> Result<Element, SubplotError> {
let dot = e.content();
let svg = DotMarkup::new(&dot).as_svg()?;
- Ok(svg_to_element(svg))
+ Ok(svg_to_element(svg, "Dot diagram"))
}
fn typeset_plantuml(e: &Element) -> Result<Element, SubplotError> {
let markup = e.content();
let svg = PlantumlMarkup::new(&markup).as_svg()?;
- Ok(svg_to_element(svg))
+ Ok(svg_to_element(svg, "UML diagram"))
}
fn typeset_pikchr(e: &Element) -> Result<Element, SubplotError> {
let markup = e.content();
let svg = PikchrMarkup::new(&markup, None).as_svg()?;
- Ok(svg_to_element(svg))
+ Ok(svg_to_element(svg, "Pikchr diagram"))
}
fn typeset_roadmap(e: &Element) -> Result<Element, SubplotError> {
@@ -400,12 +404,12 @@ mod typeset {
let roadmap = roadmap::from_yaml(&yaml)?;
let dot = roadmap.format_as_dot(WIDTH)?;
let svg = DotMarkup::new(&dot).as_svg()?;
- Ok(svg_to_element(svg))
+ Ok(svg_to_element(svg, "Road map"))
}
- fn svg_to_element(svg: Svg) -> Element {
+ fn svg_to_element(svg: Svg, alt: &str) -> Element {
let url = svg_as_data_url(svg);
- let img = html_img(&url);
+ let img = html_img(&url, alt);
html_p(vec![Content::Elt(img)])
}
@@ -422,9 +426,10 @@ mod typeset {
new
}
- fn html_img(src: &str) -> Element {
+ fn html_img(src: &str, alt: &str) -> Element {
let mut new = Element::new(ElementTag::Img);
new.push_attribute(Attribute::new("src", src));
+ new.push_attribute(Attribute::new("alt", alt));
new
}
}