fix(typlite): nodes nested in HTML element should render as HTML, disable strict mode (#1767)
Some checks are pending
tinymist::ci / Duplicate Actions Detection (push) Waiting to run
tinymist::ci / Check Clippy, Formatting, Completion, Documentation, and Tests (Linux) (push) Waiting to run
tinymist::ci / Check Minimum Rust version and Tests (Windows) (push) Waiting to run
tinymist::ci / E2E Tests (darwin-arm64 on macos-latest) (push) Blocked by required conditions
tinymist::ci / E2E Tests (linux-x64 on ubuntu-22.04) (push) Blocked by required conditions
tinymist::ci / E2E Tests (linux-x64 on ubuntu-latest) (push) Blocked by required conditions
tinymist::ci / E2E Tests (win32-x64 on windows-2019) (push) Blocked by required conditions
tinymist::ci / E2E Tests (win32-x64 on windows-latest) (push) Blocked by required conditions
tinymist::ci / prepare-build (push) Waiting to run
tinymist::ci / build-binary (push) Blocked by required conditions
tinymist::ci / build-vsc-assets (push) Blocked by required conditions
tinymist::ci / build-vscode (push) Blocked by required conditions
tinymist::ci / build-vscode-others (push) Blocked by required conditions
tinymist::ci / publish-vscode (push) Blocked by required conditions
tinymist::gh_pages / build-gh-pages (push) Waiting to run

* fix: nodes nested in HTML element should render as HTML

* fix: set strict mode to false to prevent panic

* chore: update cmark-writer to 0.7.3
This commit is contained in:
Hong Jiarong 2025-05-21 00:03:45 +08:00 committed by GitHub
parent a33ab6a79b
commit dd03bf22c8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 116 additions and 40 deletions

26
Cargo.lock generated
View file

@ -581,18 +581,21 @@ dependencies = [
[[package]]
name = "cmark-writer"
version = "0.6.3"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af6cd7a429a8ab9b066867dcb420f71b4eb36566d05d764ffbfff5aee1a84b05"
checksum = "fb6dce3a4430a75a3903f4a25729ba67b4476c39e2ed6d907ab2e5bfcce03a36"
dependencies = [
"cmark-writer-macros",
"env_logger",
"html-escape",
"log",
]
[[package]]
name = "cmark-writer-macros"
version = "0.6.3"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01be201bdfedabd81a11363ab87c1bc92baedf90039437bede5eee0ea9ece8d5"
checksum = "6ee5c1417934f17ae1605a2f5f34fbb55dea86c728c717008395add125561f7a"
dependencies = [
"proc-macro2",
"quote",
@ -1549,6 +1552,15 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "html-escape"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476"
dependencies = [
"utf8-width",
]
[[package]]
name = "http"
version = "1.3.1"
@ -5308,6 +5320,12 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
[[package]]
name = "utf8-width"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3"
[[package]]
name = "utf8_iter"
version = "1.0.4"

View file

@ -97,7 +97,7 @@ rpds = "1"
# Data/Text Format and Processing
biblatex = "0.10"
cmark-writer = { version = "0.6.3", features = ["gfm"] }
cmark-writer = { version = "0.7.3", features = ["gfm"] }
docx-rs = { git = "https://github.com/Myriad-Dreamin/docx-rs", default-features = false, rev = "db49a729f68dbdb9e8e91857fbb1c3d414209871" }
hayagriva = "0.8"
hex = "0.4.3"

View file

@ -1,8 +1,15 @@
//! Common types and interfaces for the conversion system
use cmark_writer::ast::{CustomNodeWriter, Node};
use cmark_writer::ast::Node;
use cmark_writer::custom_node;
use cmark_writer::CommonMarkWriter;
use cmark_writer::HtmlAttribute;
use cmark_writer::HtmlElement;
use cmark_writer::HtmlWriteResult;
use cmark_writer::HtmlWriter;
use cmark_writer::HtmlWriterOptions;
use cmark_writer::WriteResult;
use cmark_writer::WriterOptions;
use ecow::EcoString;
use std::path::PathBuf;
@ -27,7 +34,7 @@ pub enum Format {
/// Figure node implementation for all formats
#[derive(Debug, PartialEq, Clone)]
#[custom_node]
#[custom_node(block = true, html_impl = true)]
pub struct FigureNode {
/// The main content of the figure, can be any block node
pub body: Box<Node>,
@ -36,8 +43,11 @@ pub struct FigureNode {
}
impl FigureNode {
fn write_custom(&self, writer: &mut dyn CustomNodeWriter) -> WriteResult<()> {
let mut temp_writer = cmark_writer::writer::CommonMarkWriter::new();
fn write_custom(&self, writer: &mut CommonMarkWriter) -> WriteResult<()> {
let mut temp_writer = CommonMarkWriter::with_options(WriterOptions {
strict: false,
..Default::default()
});
temp_writer.write(&self.body)?;
let content = temp_writer.into_string();
writer.write_str(&content)?;
@ -46,14 +56,25 @@ impl FigureNode {
Ok(())
}
fn is_block_custom(&self) -> bool {
true
fn write_html_custom(&self, writer: &mut HtmlWriter) -> HtmlWriteResult<()> {
let body = self.body.clone();
let node = Node::HtmlElement(HtmlElement {
tag: "figure".to_string(),
attributes: vec![HtmlAttribute {
name: "class".to_string(),
value: "figure".to_string(),
}],
children: vec![*body],
self_closing: false,
});
writer.write_node(&node)?;
Ok(())
}
}
/// External Frame node for handling frames stored as external files
#[derive(Debug, PartialEq, Clone)]
#[custom_node]
#[custom_node(block = true, html_impl = true)]
pub struct ExternalFrameNode {
/// The path to the external file containing the frame
pub file_path: PathBuf,
@ -64,7 +85,7 @@ pub struct ExternalFrameNode {
}
impl ExternalFrameNode {
fn write_custom(&self, writer: &mut dyn CustomNodeWriter) -> WriteResult<()> {
fn write_custom(&self, writer: &mut CommonMarkWriter) -> WriteResult<()> {
// The actual handling is implemented in format-specific writers
writer.write_str(&format!(
"![{}]({})",
@ -74,22 +95,41 @@ impl ExternalFrameNode {
Ok(())
}
fn is_block_custom(&self) -> bool {
true
fn write_html_custom(&self, writer: &mut HtmlWriter) -> HtmlWriteResult<()> {
let node = Node::HtmlElement(HtmlElement {
tag: "img".to_string(),
attributes: vec![
HtmlAttribute {
name: "src".to_string(),
value: self.file_path.display().to_string(),
},
HtmlAttribute {
name: "alt".to_string(),
value: self.alt_text.clone(),
},
],
children: vec![],
self_closing: true,
});
writer.write_node(&node)?;
Ok(())
}
}
/// Highlight node for highlighted text
#[derive(Debug, PartialEq, Clone)]
#[custom_node]
#[custom_node(block = false, html_impl = true)]
pub struct HighlightNode {
/// The content to be highlighted
pub content: Vec<Node>,
}
impl HighlightNode {
fn write_custom(&self, writer: &mut dyn CustomNodeWriter) -> WriteResult<()> {
let mut temp_writer = cmark_writer::writer::CommonMarkWriter::new();
fn write_custom(&self, writer: &mut CommonMarkWriter) -> WriteResult<()> {
let mut temp_writer = CommonMarkWriter::with_options(WriterOptions {
strict: false,
..Default::default()
});
for node in &self.content {
temp_writer.write(node)?;
}
@ -98,14 +138,21 @@ impl HighlightNode {
Ok(())
}
fn is_block_custom(&self) -> bool {
false
fn write_html_custom(&self, writer: &mut HtmlWriter) -> HtmlWriteResult<()> {
let node = Node::HtmlElement(HtmlElement {
tag: "mark".to_string(),
attributes: vec![],
children: self.content.clone(),
self_closing: false,
});
writer.write_node(&node)?;
Ok(())
}
}
/// Node for centered content
#[derive(Debug, PartialEq, Clone)]
#[custom_node]
#[custom_node(block = true, html_impl = true)]
pub struct CenterNode {
/// The content to be centered
pub node: Node,
@ -126,8 +173,11 @@ impl CenterNode {
}
}
fn write_custom(&self, writer: &mut dyn CustomNodeWriter) -> WriteResult<()> {
let mut temp_writer = cmark_writer::writer::CommonMarkWriter::new();
fn write_custom(&self, writer: &mut CommonMarkWriter) -> WriteResult<()> {
let mut temp_writer = CommonMarkWriter::with_options(WriterOptions {
strict: false,
..Default::default()
});
temp_writer.write(&self.node)?;
let content = temp_writer.into_string();
writer.write_str(&content)?;
@ -135,8 +185,15 @@ impl CenterNode {
Ok(())
}
fn is_block_custom(&self) -> bool {
true
fn write_html_custom(&self, writer: &mut HtmlWriter) -> HtmlWriteResult<()> {
let mut temp_writer = HtmlWriter::with_options(HtmlWriterOptions {
strict: false,
..Default::default()
});
temp_writer.write_node(&self.node)?;
let content = temp_writer.into_string();
writer.write_str(&content)?;
Ok(())
}
}

View file

@ -2,6 +2,7 @@
source: crates/typlite/src/tests.rs
expression: "conv(world, ConvKind::Md { for_docs: false })"
input_file: crates/typlite/src/fixtures/integration/figure_caption.typ
snapshot_kind: text
---
<!DOCTYPE html>
<html>
@ -13,7 +14,5 @@ input_file: crates/typlite/src/fixtures/integration/figure_caption.typ
</html>
=====
<p align="center">![Content](./fig.svg)
Caption
</p>
<p align="center"><figure class="figure"><p><img src="./fig.svg" alt="Content" /></p>
</figure></p>

View file

@ -2,6 +2,7 @@
source: crates/typlite/src/tests.rs
expression: "conv(world, ConvKind::Md { for_docs: false })"
input_file: crates/typlite/src/fixtures/integration/figure_image.typ
snapshot_kind: text
---
<!DOCTYPE html>
<html>
@ -13,6 +14,5 @@ input_file: crates/typlite/src/fixtures/integration/figure_image.typ
</html>
=====
<p align="center">![](./fig.svg)
</p>
<p align="center"><figure class="figure"><p><img src="./fig.svg" alt="" /></p>
</figure></p>

View file

@ -2,6 +2,7 @@
source: crates/typlite/src/tests.rs
expression: "conv(world, ConvKind::Md { for_docs: false })"
input_file: crates/typlite/src/fixtures/integration/figure_image_alt.typ
snapshot_kind: text
---
<!DOCTYPE html>
<html>
@ -13,6 +14,5 @@ input_file: crates/typlite/src/fixtures/integration/figure_image_alt.typ
</html>
=====
<p align="center">![Content](./fig.svg)
</p>
<p align="center"><figure class="figure"><p><img src="./fig.svg" alt="Content" /></p>
</figure></p>

View file

@ -1,6 +1,7 @@
//! HTML parser core, containing main structures and general parsing logic
use cmark_writer::ast::{CustomNode, HtmlAttribute, HtmlElement as CmarkHtmlElement, Node};
use cmark_writer::{CommonMarkWriter, WriteResult};
use typst::html::{tag, HtmlElement, HtmlNode};
use crate::attributes::{HeadingAttr, RawAttr, TypliteAttrsParser};
@ -260,10 +261,7 @@ impl CustomNode for Comment {
self
}
fn write(
&self,
writer: &mut dyn cmark_writer::CustomNodeWriter,
) -> cmark_writer::WriteResult<()> {
fn write(&self, writer: &mut CommonMarkWriter) -> WriteResult<()> {
writer.write_str("<!-- ")?;
writer.write_str(&self.0)?;
writer.write_str(" -->")?;

View file

@ -2,6 +2,7 @@
use cmark_writer::ast::Node;
use cmark_writer::writer::CommonMarkWriter;
use cmark_writer::WriterOptions;
use ecow::EcoString;
use crate::common::FormatWriter;
@ -19,7 +20,10 @@ impl MarkdownWriter {
impl FormatWriter for MarkdownWriter {
fn write_eco(&mut self, document: &Node, output: &mut EcoString) -> Result<()> {
let mut writer = CommonMarkWriter::new();
let mut writer = CommonMarkWriter::with_options(WriterOptions {
strict: false,
..Default::default()
});
writer
.write(document)
.map_err(|e| format!("failed to write document: {}", e))?;