work rust
authorBen Pfaff <blp@cs.stanford.edu>
Sun, 21 Dec 2025 01:38:34 +0000 (17:38 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Sun, 21 Dec 2025 01:38:34 +0000 (17:38 -0800)
rust/pspp/src/spv/read/html.rs

index c4cb7ba7c4eaf2743c7abf715fdb0e97b266e0fc..19dc7097669358e8b62c184232e56114ab92ef69 100644 (file)
@@ -269,29 +269,73 @@ impl Markup {
                 writer.write_event(Event::Text(BytesText::new(&format!("&[{name}]"))))?
             }
             Markup::Style { style, child } => {
-                match style {
-                    Style::Bold => writer.create_element("b"),
-                    Style::Italic => writer.create_element("i"),
-                    Style::Underline => writer.create_element("u"),
-                    Style::Strike => writer.create_element("strike"),
-                    Style::Emphasis => writer.create_element("em"),
-                    Style::Strong => writer.create_element("strong"),
-                    Style::Face(face) => writer
-                        .create_element("font")
-                        .with_attribute(("face", face.as_str())),
-                    Style::Color(color) => writer
-                        .create_element("font")
-                        .with_attribute(("color", color.display_css().to_string().as_str())),
-                    Style::Size(points) => writer
-                        .create_element("font")
-                        .with_attribute(("size", format!("{}pt", *points / 0.75).as_str())),
+                let mut elements = Vec::new();
+                let mut attributes = Vec::new();
+                fn add_style(
+                    style: &Style,
+                    elements: &mut Vec<&'static str>,
+                    attributes: &mut Vec<(&'static str, String)>,
+                ) {
+                    match style {
+                        Style::Bold => elements.push("b"),
+                        Style::Italic => elements.push("i"),
+                        Style::Underline => elements.push("u"),
+                        Style::Strike => elements.push("strike"),
+                        Style::Emphasis => elements.push("em"),
+                        Style::Strong => elements.push("strong"),
+                        Style::Face(face) => attributes.push(("face", face.clone())),
+                        Style::Color(color) => {
+                            attributes.push(("color", color.display_css().to_string()))
+                        }
+                        Style::Size(points) => {
+                            attributes.push(("size", format!("{}pt", *points / 0.75)))
+                        }
+                    }
                 }
-                .write_inner_content(|w| child.write_html(w))?;
+
+                add_style(style, &mut elements, &mut attributes);
+                let mut next = &**child;
+                while let Markup::Style { style, child } = next {
+                    add_style(style, &mut elements, &mut attributes);
+                    next = &**child;
+                }
+
+                elements.sort();
+                attributes.sort();
+                next.write_styles(writer, &elements, &attributes)?;
             }
         }
         Ok(())
     }
 
+    fn write_styles<X>(
+        &self,
+        writer: &mut XmlWriter<X>,
+        elements: &[&str],
+        attributes: &[(&str, String)],
+    ) -> std::io::Result<()>
+    where
+        X: Write,
+    {
+        if !attributes.is_empty() {
+            writer
+                .create_element("font")
+                .with_attributes(
+                    attributes
+                        .into_iter()
+                        .map(|(name, value)| (*name, Cow::from(value))),
+                )
+                .write_inner_content(|w| self.write_styles(w, elements, &[]))?;
+        } else if let Some((element, rest)) = elements.split_first() {
+            writer
+                .create_element(*element)
+                .write_inner_content(|w| self.write_styles(w, rest, attributes))?;
+        } else {
+            self.write_html(writer)?;
+        }
+        Ok(())
+    }
+
     /// Returns this markup converted into XHTML.  The returned string contains
     /// a single `<html>...</html>` element.
     ///
@@ -901,7 +945,7 @@ mod tests {
         let content = quick_xml::de::from_str::<String>(text).unwrap();
         assert_eq!(
             Document::from_html(&content).to_html(),
-            r##"<p align="left">plain <font size="9pt"><font color="#000000"><font face="Monospaced"><b>bold</b></font></font></font> <font size="9pt"><font color="#000000"><font face="Monospaced"><i>italic</i> <strike>strikeout</strike></font></font></font></p>"##
+            r##"<p align="left">plain <font color="#000000" face="Monospaced" size="9pt"><b>bold</b></font> <font color="#000000" face="Monospaced" size="9pt"><i>italic</i> <strike>strikeout</strike></font></p>"##
         );
     }
 
@@ -922,7 +966,7 @@ mod tests {
         let content = quick_xml::de::from_str::<String>(text).unwrap();
         assert_eq!(
             Document::from_html(&content).to_html(),
-            r##"<p align="left">left</p><p align="center"><font size="13.5pt"><font color="#000000"><font face="Monospaced">center large</font></font></font></p><p align="right"><font size="9pt"><font color="#000000"><font face="Monospaced"><b><i>right</i></b></font></font></font></p>"##
+            r##"<p align="left">left</p><p align="center"><font color="#000000" face="Monospaced" size="13.5pt">center large</font></p><p align="right"><font color="#000000" face="Monospaced" size="9pt"><b><i>right</i></b></font></p>"##
         );
     }
 
@@ -1007,7 +1051,7 @@ mod tests {
         let document = Document::from_html(&content);
         assert_eq!(
             document.to_html(),
-            r##"<p align="center"><font color="#000000"><font size="10pt"><font face="sans-serif">&amp;[PageTitle]</font></font></font></p>"##
+            r##"<p align="center"><font color="#000000" face="sans-serif" size="10pt">&amp;[PageTitle]</font></p>"##
         );
         assert_eq!(
             document.0[0]
@@ -1042,7 +1086,7 @@ mod tests {
         let html = Document::from_html(&content);
         assert_eq!(
             html.to_html(),
-            r##"<p align="right"><font color="#000000"><font size="10pt"><font face="sans-serif">Page &amp;[Page]</font></font></font></p>"##
+            r##"<p align="right"><font color="#000000" face="sans-serif" size="10pt">Page &amp;[Page]</font></p>"##
         );
     }