feat: add BlockVerbatimNode for block-level raw text output and update related handling in parser and writers

This commit is contained in:
Hong Jiarong 2025-11-18 11:43:29 +08:00
parent 32f1543343
commit 4a0edcbda3
7 changed files with 63 additions and 28 deletions

View file

@ -208,11 +208,11 @@ impl InlineNode {
} }
} }
/// Verbatim node for raw text output /// Inline verbatim node for raw text output
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
#[custom_node(block = false, html_impl = true)] #[custom_node(block = false, html_impl = true)]
pub struct VerbatimNode { pub struct VerbatimNode {
/// The content to directly output /// The content to directly output inline
pub content: EcoString, pub content: EcoString,
} }
@ -226,6 +226,25 @@ impl VerbatimNode {
} }
} }
/// Block verbatim node for raw text output without paragraph wrapping
#[derive(Debug, PartialEq, Clone)]
#[custom_node(block = true, html_impl = true)]
pub struct BlockVerbatimNode {
/// The content to directly output as a block
pub content: EcoString,
}
impl BlockVerbatimNode {
fn write_custom(&self, writer: &mut BlockWriterProxy) -> WriteResult<()> {
writer.write_str(&self.content)?;
Ok(())
}
fn write_html_custom(&self, writer: &mut HtmlWriter) -> HtmlWriteResult<()> {
writer.write_trusted_html(&self.content)
}
}
/// Alert node for alert messages /// Alert node for alert messages
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
#[custom_node(block = true, html_impl = false)] #[custom_node(block = true, html_impl = false)]

View file

@ -64,7 +64,7 @@ separately rendered SVG
</td><td> </td><td>
<p>```` ````
== Under the Greenwood Tree == Under the Greenwood Tree
by Shakespeare. by Shakespeare.
@ -74,8 +74,7 @@ Under the greenwood tree #emoji.tree
Who loves to lie with me, Who loves to lie with me,
... ...
```) ```)
````</p> ````
</td> </td>

View file

@ -3,4 +3,4 @@ source: crates/typlite/src/tests.rs
expression: hash expression: hash
input_file: crates/typlite/src/fixtures/integration/issue-1844.typ input_file: crates/typlite/src/fixtures/integration/issue-1844.typ
--- ---
siphash128_13:5680ee2c48f93da5b40c162e391ef15 siphash128_13:b700ae0ff0daf675a40354416190674e

View file

@ -14,10 +14,8 @@ use typst_html::{HtmlElement, HtmlNode, tag};
use crate::Result; use crate::Result;
use crate::TypliteFeat; use crate::TypliteFeat;
use crate::attributes::{ use crate::attributes::{AlertsAttr, HeadingAttr, RawAttr, TypliteAttrsParser, VerbatimAttr};
AlertsAttr, HeadingAttr, RawAttr, TypliteAttrsParser, VerbatimAttr, md_attr, use crate::common::{AlertNode, BlockVerbatimNode, CenterNode, VerbatimNode};
};
use crate::common::{AlertNode, CenterNode, VerbatimNode};
use crate::diagnostics::WarningCollector; use crate::diagnostics::WarningCollector;
use crate::tags::md_tag; use crate::tags::md_tag;
@ -196,9 +194,9 @@ impl HtmlToAstParser {
let attrs = VerbatimAttr::parse(&element.attrs)?; let attrs = VerbatimAttr::parse(&element.attrs)?;
if attrs.block { if attrs.block {
self.flush_inline_buffer(); self.flush_inline_buffer();
self.blocks.push(Node::Paragraph(vec![Node::Custom(Box::new( self.blocks.push(Node::Custom(Box::new(BlockVerbatimNode {
VerbatimNode { content: attrs.src }, content: attrs.src,
))])); })));
} else { } else {
self.inline_buffer self.inline_buffer
.push(Node::Custom(Box::new(VerbatimNode { content: attrs.src }))); .push(Node::Custom(Box::new(VerbatimNode { content: attrs.src })));
@ -405,12 +403,8 @@ impl HtmlToAstParser {
} }
fn is_verbatim_block(element: &HtmlElement) -> bool { fn is_verbatim_block(element: &HtmlElement) -> bool {
element VerbatimAttr::parse(&element.attrs)
.attrs .map(|attrs| attrs.block)
.0
.iter()
.find(|(name, _)| *name == md_attr::block)
.and_then(|(_, value)| value.parse::<bool>().ok())
.unwrap_or(false) .unwrap_or(false)
} }

View file

@ -4,13 +4,14 @@ use base64::Engine;
use cmark_writer::ast::{ListItem, Node}; use cmark_writer::ast::{ListItem, Node};
use docx_rs::*; use docx_rs::*;
use ecow::EcoString; use ecow::EcoString;
use log::{debug, warn}; use log::debug;
use std::fs; use std::fs;
use std::io::Cursor; use std::io::Cursor;
use crate::Result; use crate::Result;
use crate::common::{ use crate::common::{
CenterNode, FigureNode, FormatWriter, HighlightNode, InlineNode, VerbatimNode, BlockVerbatimNode, CenterNode, FigureNode, FormatWriter, HighlightNode, InlineNode,
VerbatimNode,
}; };
use super::image_processor::DocxImageProcessor; use super::image_processor::DocxImageProcessor;
@ -253,10 +254,7 @@ impl DocxWriter {
} }
node if node.is_custom_type::<VerbatimNode>() => { node if node.is_custom_type::<VerbatimNode>() => {
let node = node.as_custom_type::<VerbatimNode>().unwrap(); let node = node.as_custom_type::<VerbatimNode>().unwrap();
warn!( run = run.style("CodeInline").add_text(&node.content);
"ignoring `m1verbatim` content in DOCX export: {:?}",
node.content
);
} }
// Other inline element types // Other inline element types
_ => { _ => {
@ -504,6 +502,15 @@ impl DocxWriter {
docx = docx.add_paragraph(para); docx = docx.add_paragraph(para);
} }
} }
node if node.is_custom_type::<BlockVerbatimNode>() => {
let block_node = node.as_custom_type::<BlockVerbatimNode>().unwrap();
for line in block_node.content.split('\n') {
let para = Paragraph::new()
.style("CodeBlock")
.add_run(Run::new().add_text(line));
docx = docx.add_paragraph(para);
}
}
node if node.is_custom_type::<InlineNode>() => { node if node.is_custom_type::<InlineNode>() => {
let inline_node = node.as_custom_type::<InlineNode>().unwrap(); let inline_node = node.as_custom_type::<InlineNode>().unwrap();
// Handle InlineNode at block level (convert to paragraph) // Handle InlineNode at block level (convert to paragraph)

View file

@ -8,8 +8,8 @@ use tinymist_std::path::unix_slash;
use crate::Result; use crate::Result;
use crate::common::{ use crate::common::{
CenterNode, ExternalFrameNode, FigureNode, FormatWriter, HighlightNode, InlineNode, ListState, BlockVerbatimNode, CenterNode, ExternalFrameNode, FigureNode, FormatWriter, HighlightNode,
VerbatimNode, InlineNode, ListState, VerbatimNode,
}; };
/// LaTeX writer implementation /// LaTeX writer implementation
@ -289,6 +289,11 @@ impl LaTeXWriter {
self.write_node(child, output)?; self.write_node(child, output)?;
} }
} }
node if node.is_custom_type::<BlockVerbatimNode>() => {
let block_node = node.as_custom_type::<BlockVerbatimNode>().unwrap();
output.push_str(&block_node.content);
output.push_str("\n\n");
}
node if node.is_custom_type::<VerbatimNode>() => { node if node.is_custom_type::<VerbatimNode>() => {
let inline_node = node.as_custom_type::<VerbatimNode>().unwrap(); let inline_node = node.as_custom_type::<VerbatimNode>().unwrap();
output.push_str(&inline_node.content); output.push_str(&inline_node.content);

View file

@ -4,7 +4,7 @@ use cmark_writer::ast::Node;
use ecow::EcoString; use ecow::EcoString;
use crate::Result; use crate::Result;
use crate::common::{ExternalFrameNode, FigureNode, FormatWriter}; use crate::common::{BlockVerbatimNode, ExternalFrameNode, FigureNode, FormatWriter, VerbatimNode};
/// Text writer implementation /// Text writer implementation
#[derive(Default)] #[derive(Default)]
@ -161,6 +161,12 @@ impl TextWriter {
output.push_str(&external_frame.alt_text); output.push_str(&external_frame.alt_text);
} }
} }
node if node.is_custom_type::<BlockVerbatimNode>() => {
if let Some(block_node) = node.as_custom_type::<BlockVerbatimNode>() {
output.push_str(&block_node.content);
output.push_str("\n\n");
}
}
node if node.is_custom_type::<crate::common::HighlightNode>() => { node if node.is_custom_type::<crate::common::HighlightNode>() => {
if let Some(highlight) = node.as_custom_type::<crate::common::HighlightNode>() { if let Some(highlight) = node.as_custom_type::<crate::common::HighlightNode>() {
for child in &highlight.content { for child in &highlight.content {
@ -168,6 +174,11 @@ impl TextWriter {
} }
} }
} }
node if node.is_custom_type::<VerbatimNode>() => {
if let Some(inline_node) = node.as_custom_type::<VerbatimNode>() {
output.push_str(&inline_node.content);
}
}
_ => {} _ => {}
} }
Ok(()) Ok(())