1use crate::{
2 diagnostic::{Diagnostic, Diagnostics},
3 model::*,
4 validate::validate_and_canonicalize,
5};
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
11pub struct AppleWireDocument {
12 #[serde(rename = "type")]
13 pub wire_type: String,
14 pub version: u32,
15 pub blocks: Vec<AppleWireBlock>,
16}
17
18#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
22pub struct AppleWireRun {
23 pub text: String,
24 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub bold: Option<bool>,
26}
27
28#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
30pub struct AppleWireParagraph {
31 pub runs: Vec<AppleWireRun>,
32}
33
34#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
38#[serde(tag = "type")]
39pub enum AppleWireBlock {
40 #[serde(rename = "paragraph")]
41 Paragraph { runs: Vec<AppleWireRun> },
42 #[serde(rename = "heading")]
43 Heading { level: u8, runs: Vec<AppleWireRun> },
44 #[serde(rename = "blockquote")]
45 Blockquote { paragraphs: Vec<AppleWireParagraph> },
46 #[serde(rename = "bullet_list")]
47 BulletList { items: Vec<AppleWireParagraph> },
48 #[serde(rename = "ordered_list")]
49 OrderedList {
50 start: u32,
51 items: Vec<AppleWireParagraph>,
52 },
53}
54
55pub fn from_apple(value: &Value) -> Result<WdfDocument, Diagnostics> {
74 let wire = serde_json::from_value::<AppleWireDocument>(value.clone()).map_err(|error| {
75 vec![Diagnostic::error(
76 "apple",
77 "apple.invalid_document",
78 error.to_string(),
79 )]
80 })?;
81 if wire.wire_type != "apple.document" || wire.version != 1 {
82 return Err(vec![Diagnostic::error(
83 "apple",
84 "apple.invalid_document",
85 "type must be apple.document and version must be 1",
86 )]);
87 }
88 let blocks = wire
89 .blocks
90 .into_iter()
91 .map(|block| match block {
92 AppleWireBlock::Paragraph { runs } => WdfBlock::Paragraph {
93 content: runs_to_wdf(runs),
94 },
95 AppleWireBlock::Heading { level, runs } => WdfBlock::Heading {
96 attrs: HeadingAttrs { level },
97 content: runs_to_wdf(runs),
98 },
99 AppleWireBlock::Blockquote { paragraphs } => WdfBlock::Blockquote {
100 content: paragraphs
101 .into_iter()
102 .map(|p| paragraph(runs_to_wdf(p.runs)))
103 .collect(),
104 },
105 AppleWireBlock::BulletList { items } => WdfBlock::BulletList {
106 content: items
107 .into_iter()
108 .map(|p| list_item(runs_to_wdf(p.runs)))
109 .collect(),
110 },
111 AppleWireBlock::OrderedList { start, items } => WdfBlock::OrderedList {
112 attrs: Some(OrderedListAttrs { start }),
113 content: items
114 .into_iter()
115 .map(|p| list_item(runs_to_wdf(p.runs)))
116 .collect(),
117 },
118 })
119 .collect::<Vec<_>>();
120 validate_and_canonicalize(
121 &serde_json::to_value(WdfDocument {
122 schema: WDF_SCHEMA.to_string(),
123 version: WDF_VERSION,
124 root: WdfRoot {
125 node_type: "doc".to_string(),
126 content: blocks,
127 },
128 })
129 .expect("WDF serialization"),
130 )
131}
132
133fn runs_to_wdf(runs: Vec<AppleWireRun>) -> Vec<WdfText> {
134 runs.into_iter()
135 .map(|run| {
136 if run.bold == Some(true) {
137 WdfText::bold(run.text)
138 } else {
139 WdfText::plain(run.text)
140 }
141 })
142 .collect()
143}
144fn runs(content: &[WdfText]) -> Vec<AppleWireRun> {
145 content
146 .iter()
147 .filter(|text| !text.text.is_empty())
148 .map(|text| AppleWireRun {
149 text: text.text.clone(),
150 bold: text.is_bold().then_some(true),
151 })
152 .collect()
153}
154
155pub fn to_apple(document: &WdfDocument) -> Value {
160 let wire = AppleWireDocument {
161 wire_type: "apple.document".to_string(),
162 version: 1,
163 blocks: document
164 .root
165 .content
166 .iter()
167 .map(|block| match block {
168 WdfBlock::Paragraph { content } => AppleWireBlock::Paragraph {
169 runs: runs(content),
170 },
171 WdfBlock::Heading { attrs, content } => AppleWireBlock::Heading {
172 level: attrs.level,
173 runs: runs(content),
174 },
175 WdfBlock::Blockquote { content } => AppleWireBlock::Blockquote {
176 paragraphs: content
177 .iter()
178 .map(|p| AppleWireParagraph {
179 runs: runs(&p.content),
180 })
181 .collect(),
182 },
183 WdfBlock::BulletList { content } => AppleWireBlock::BulletList {
184 items: content
185 .iter()
186 .map(|i| AppleWireParagraph {
187 runs: runs(&i.content[0].content),
188 })
189 .collect(),
190 },
191 WdfBlock::OrderedList { attrs, content } => AppleWireBlock::OrderedList {
192 start: attrs.as_ref().map_or(1, |a| a.start),
193 items: content
194 .iter()
195 .map(|i| AppleWireParagraph {
196 runs: runs(&i.content[0].content),
197 })
198 .collect(),
199 },
200 })
201 .collect(),
202 };
203 serde_json::to_value(wire).expect("Apple wire serialization")
204}