Skip to main content

typub_html/
serialize_rules.rs

1//! Platform-specific serialization options.
2//!
3//! These rules control HTML output generation at serialization time.
4//! SerializeRule provides a declarative way to specify which output
5//! variations each platform needs.
6
7use enumset::{EnumSet, EnumSetType};
8
9/// Platform-specific serialization rules.
10///
11/// These rules are applied during Stage-5 (Serialize) to control HTML output.
12/// Each rule can be enabled per-platform via the `serialize_rules` field
13/// in `profiles.toml`.
14#[derive(EnumSetType, Debug)]
15pub enum SerializeRule {
16    /// Wrap `<li>` content in `<span style="display:inline;">`.
17    ///
18    /// Needed for: WeChat (prevents text splitting in list items).
19    /// WeChat's editor sometimes breaks list item content across multiple
20    /// elements; wrapping in an inline span keeps the content together.
21    LiSpanWrap,
22    /// Use `<blockquote>` for admonitions (instead of `<div>`).
23    ///
24    /// Needed for: WeChat, Weibo (these platforms strip `<div>` and `<section>` tags).
25    /// When enabled, admonitions render as `<blockquote class="admonition note">`
26    /// instead of `<div class="admonition note">`. The `<blockquote>` tag is
27    /// preserved by editors that filter other container elements.
28    BlockquoteForAdmonition,
29    /// Use sibling `<ul>`/`<ol>` instead of nested children of `<li>`.
30    ///
31    /// Needed for: WeChat (its ProseMirror editor transforms nested `<ul>` incorrectly,
32    /// causing reordering for 3+ levels of nesting).
33    ///
34    /// Standard HTML: `<ul><li>Item<ul><li>Nested</li></ul></li></ul>`
35    /// WeChat's structure: `<ul><li>Item</li><ul><li>Nested</li></ul></ul>`
36    ///
37    /// Note: This produces technically invalid HTML (per spec, `<ul>` can only
38    /// contain `<li>` as direct children), but it's what WeChat's editor expects.
39    SiblingNestedLists,
40    /// Convert definition lists (`<dl>`) to paragraphs.
41    ///
42    /// Needed for: WeChat, Weibo (these platforms don't support `<dl>`, `<dt>`, `<dd>` tags).
43    /// When enabled, each definition item renders as:
44    /// `<p><strong>Term</strong>: Definition</p>`
45    /// instead of:
46    /// `<dl><dt>Term</dt><dd>Definition</dd></dl>`
47    DefinitionListToParagraph,
48}
49
50/// A set of serialization rules to apply.
51pub type SerializeRules = EnumSet<SerializeRule>;
52
53/// Parse serialization rules from a string slice array.
54///
55/// Used by build.rs to convert TOML array values to SerializeRules.
56/// Unknown rule names are silently ignored.
57pub fn parse_serialize_rules(names: &[&str]) -> SerializeRules {
58    let mut rules = SerializeRules::empty();
59    for name in names {
60        // Unknown rules are silently ignored
61        if *name == "li_span_wrap" {
62            rules |= SerializeRule::LiSpanWrap;
63        }
64        if *name == "blockquote_for_admonition" {
65            rules |= SerializeRule::BlockquoteForAdmonition;
66        }
67        if *name == "sibling_nested_lists" {
68            rules |= SerializeRule::SiblingNestedLists;
69        }
70        if *name == "definition_list_to_paragraph" {
71            rules |= SerializeRule::DefinitionListToParagraph;
72        }
73    }
74    rules
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn test_parse_serialize_rules() {
83        let rules = parse_serialize_rules(&["li_span_wrap"]);
84        assert!(rules.contains(SerializeRule::LiSpanWrap));
85    }
86
87    #[test]
88    fn test_parse_blockquote_for_admonition() {
89        let rules = parse_serialize_rules(&["blockquote_for_admonition"]);
90        assert!(rules.contains(SerializeRule::BlockquoteForAdmonition));
91    }
92
93    #[test]
94    fn test_parse_sibling_nested_lists() {
95        let rules = parse_serialize_rules(&["sibling_nested_lists"]);
96        assert!(rules.contains(SerializeRule::SiblingNestedLists));
97    }
98
99    #[test]
100    fn test_parse_definition_list_to_paragraph() {
101        let rules = parse_serialize_rules(&["definition_list_to_paragraph"]);
102        assert!(rules.contains(SerializeRule::DefinitionListToParagraph));
103    }
104
105    #[test]
106    fn test_parse_unknown_rule() {
107        let rules = parse_serialize_rules(&["unknown_rule"]);
108        assert!(rules.is_empty());
109    }
110
111    #[test]
112    fn test_parse_multiple_rules() {
113        let rules =
114            parse_serialize_rules(&["li_span_wrap", "blockquote_for_admonition", "unknown"]);
115        assert_eq!(rules.len(), 2);
116        assert!(rules.contains(SerializeRule::LiSpanWrap));
117        assert!(rules.contains(SerializeRule::BlockquoteForAdmonition));
118    }
119}