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}