Skip to main content

panache_parser/parser/inlines/
raw_inline.rs

1//! Parsing for inline raw spans (`content`{=format})
2//!
3//! Raw inline spans allow embedding raw content for specific output formats.
4//! Syntax: `content`{=format}
5//! Examples:
6//! - `<a>html</a>`{=html}
7//! - `\LaTeX`{=latex}
8//! - `<w:br/>`{=openxml}
9//!
10//! This is enabled by the raw_attribute extension.
11
12use crate::parser::utils::attributes::AttributeBlock;
13use crate::syntax::SyntaxKind;
14use rowan::GreenNodeBuilder;
15
16/// Check if a code span with attributes is actually a raw inline span.
17/// Raw inline spans have attributes of the form {=format} (no other attributes).
18pub fn is_raw_inline(attributes: &AttributeBlock) -> Option<&str> {
19    // Raw inline must have exactly one class starting with '='
20    // and no identifier or key-value pairs
21    if attributes.identifier.is_some() || !attributes.key_values.is_empty() {
22        return None;
23    }
24
25    if attributes.classes.len() == 1 {
26        let class = &attributes.classes[0];
27        if let Some(format) = class.strip_prefix('=')
28            && !format.is_empty()
29        {
30            return Some(format);
31        }
32    }
33
34    None
35}
36
37/// Emit a raw inline span node to the builder.
38pub fn emit_raw_inline(
39    builder: &mut GreenNodeBuilder,
40    content: &str,
41    backtick_count: usize,
42    format: &str,
43) {
44    builder.start_node(SyntaxKind::RAW_INLINE.into());
45
46    // Opening backticks
47    builder.token(
48        SyntaxKind::RAW_INLINE_MARKER.into(),
49        &"`".repeat(backtick_count),
50    );
51
52    // Raw content
53    builder.token(SyntaxKind::RAW_INLINE_CONTENT.into(), content);
54
55    // Closing backticks
56    builder.token(
57        SyntaxKind::RAW_INLINE_MARKER.into(),
58        &"`".repeat(backtick_count),
59    );
60
61    // Format attribute: {=format}
62    builder.start_node(SyntaxKind::ATTRIBUTE.into());
63    builder.token(SyntaxKind::TEXT.into(), &format!("{{={}}}", format));
64    builder.finish_node();
65
66    builder.finish_node();
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72    use crate::parser::utils::attributes::AttributeBlock;
73
74    #[test]
75    fn test_is_raw_inline_html() {
76        let attrs = AttributeBlock {
77            identifier: None,
78            classes: vec!["=html".to_string()],
79            key_values: vec![],
80        };
81        assert_eq!(is_raw_inline(&attrs), Some("html"));
82    }
83
84    #[test]
85    fn test_is_raw_inline_latex() {
86        let attrs = AttributeBlock {
87            identifier: None,
88            classes: vec!["=latex".to_string()],
89            key_values: vec![],
90        };
91        assert_eq!(is_raw_inline(&attrs), Some("latex"));
92    }
93
94    #[test]
95    fn test_is_raw_inline_openxml() {
96        let attrs = AttributeBlock {
97            identifier: None,
98            classes: vec!["=openxml".to_string()],
99            key_values: vec![],
100        };
101        assert_eq!(is_raw_inline(&attrs), Some("openxml"));
102    }
103
104    #[test]
105    fn test_not_raw_inline_regular_class() {
106        let attrs = AttributeBlock {
107            identifier: None,
108            classes: vec!["python".to_string()],
109            key_values: vec![],
110        };
111        assert_eq!(is_raw_inline(&attrs), None);
112    }
113
114    #[test]
115    fn test_not_raw_inline_with_id() {
116        let attrs = AttributeBlock {
117            identifier: Some("myid".to_string()),
118            classes: vec!["=html".to_string()],
119            key_values: vec![],
120        };
121        assert_eq!(is_raw_inline(&attrs), None);
122    }
123
124    #[test]
125    fn test_not_raw_inline_with_key_value() {
126        let attrs = AttributeBlock {
127            identifier: None,
128            classes: vec!["=html".to_string()],
129            key_values: vec![("key".to_string(), "value".to_string())],
130        };
131        assert_eq!(is_raw_inline(&attrs), None);
132    }
133
134    #[test]
135    fn test_not_raw_inline_multiple_classes() {
136        let attrs = AttributeBlock {
137            identifier: None,
138            classes: vec!["=html".to_string(), "other".to_string()],
139            key_values: vec![],
140        };
141        assert_eq!(is_raw_inline(&attrs), None);
142    }
143
144    #[test]
145    fn test_not_raw_inline_empty_format() {
146        let attrs = AttributeBlock {
147            identifier: None,
148            classes: vec!["=".to_string()],
149            key_values: vec![],
150        };
151        assert_eq!(is_raw_inline(&attrs), None);
152    }
153}