1use crate::error::{OxmlError, Result};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum ST_Jc {
8 Start,
9 End,
10 Center,
11 Both,
12 Distribute,
13 Left,
14 Right,
15}
16
17impl ST_Jc {
18 pub fn from_str(s: &str) -> Result<Self> {
19 match s {
20 "start" | "left" => Ok(ST_Jc::Left),
21 "end" | "right" => Ok(ST_Jc::Right),
22 "center" => Ok(ST_Jc::Center),
23 "both" | "justify" | "lowKashida" | "mediumKashida" | "highKashida" => Ok(ST_Jc::Both),
29 "distribute" => Ok(ST_Jc::Distribute),
30 _ => Err(OxmlError::InvalidValue(format!("invalid ST_Jc: {s}"))),
31 }
32 }
33
34 pub fn to_str(self) -> &'static str {
35 match self {
36 ST_Jc::Start | ST_Jc::Left => "left",
37 ST_Jc::End | ST_Jc::Right => "right",
38 ST_Jc::Center => "center",
39 ST_Jc::Both => "both",
40 ST_Jc::Distribute => "distribute",
41 }
42 }
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum ST_OnOff {
48 On,
49 Off,
50}
51
52impl ST_OnOff {
53 pub fn from_str_or_default(s: Option<&str>) -> Self {
54 match s {
55 None | Some("") | Some("true") | Some("1") | Some("on") => ST_OnOff::On,
57 Some("false") | Some("0") | Some("off") => ST_OnOff::Off,
58 Some(_) => ST_OnOff::Off,
59 }
60 }
61
62 pub fn is_on(self) -> bool {
63 self == ST_OnOff::On
64 }
65
66 pub fn to_str(self) -> &'static str {
67 match self {
68 ST_OnOff::On => "true",
69 ST_OnOff::Off => "false",
70 }
71 }
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub enum ST_Underline {
77 None,
78 Single,
79 Words,
80 Double,
81 Thick,
82 Dotted,
83 Dash,
84 DotDash,
85 DotDotDash,
86 Wave,
87}
88
89impl ST_Underline {
90 pub fn from_str(s: &str) -> Result<Self> {
91 match s {
92 "none" => Ok(ST_Underline::None),
93 "single" => Ok(ST_Underline::Single),
94 "words" => Ok(ST_Underline::Words),
95 "double" => Ok(ST_Underline::Double),
96 "thick" => Ok(ST_Underline::Thick),
97 "dotted" => Ok(ST_Underline::Dotted),
98 "dash" => Ok(ST_Underline::Dash),
99 "dotDash" => Ok(ST_Underline::DotDash),
100 "dotDotDash" => Ok(ST_Underline::DotDotDash),
101 "wave" => Ok(ST_Underline::Wave),
102 _ => Err(OxmlError::InvalidValue(format!(
103 "invalid ST_Underline: {s}"
104 ))),
105 }
106 }
107
108 pub fn to_str(self) -> &'static str {
109 match self {
110 ST_Underline::None => "none",
111 ST_Underline::Single => "single",
112 ST_Underline::Words => "words",
113 ST_Underline::Double => "double",
114 ST_Underline::Thick => "thick",
115 ST_Underline::Dotted => "dotted",
116 ST_Underline::Dash => "dash",
117 ST_Underline::DotDash => "dotDash",
118 ST_Underline::DotDotDash => "dotDotDash",
119 ST_Underline::Wave => "wave",
120 }
121 }
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126pub enum ST_Border {
127 None,
128 Single,
129 Thick,
130 Double,
131 Dotted,
132 Dashed,
133 DotDash,
134 DotDotDash,
135 Triple,
136 ThinThickSmallGap,
137 ThickThinSmallGap,
138 ThinThickMediumGap,
139 ThickThinMediumGap,
140 ThinThickLargeGap,
141 ThickThinLargeGap,
142 Wave,
143 DoubleWave,
144 ThreeDEmboss,
145 ThreeDEngrave,
146 Outset,
147 Inset,
148}
149
150impl ST_Border {
151 pub fn from_str(s: &str) -> Result<Self> {
152 match s {
153 "none" | "nil" => Ok(Self::None),
154 "single" => Ok(Self::Single),
155 "thick" => Ok(Self::Thick),
156 "double" => Ok(Self::Double),
157 "dotted" => Ok(Self::Dotted),
158 "dashed" => Ok(Self::Dashed),
159 "dotDash" => Ok(Self::DotDash),
160 "dotDotDash" => Ok(Self::DotDotDash),
161 "triple" => Ok(Self::Triple),
162 "thinThickSmallGap" => Ok(Self::ThinThickSmallGap),
163 "thickThinSmallGap" => Ok(Self::ThickThinSmallGap),
164 "thinThickMediumGap" => Ok(Self::ThinThickMediumGap),
165 "thickThinMediumGap" => Ok(Self::ThickThinMediumGap),
166 "thinThickLargeGap" => Ok(Self::ThinThickLargeGap),
167 "thickThinLargeGap" => Ok(Self::ThickThinLargeGap),
168 "wave" => Ok(Self::Wave),
169 "doubleWave" => Ok(Self::DoubleWave),
170 "threeDEmboss" => Ok(Self::ThreeDEmboss),
171 "threeDEngrave" => Ok(Self::ThreeDEngrave),
172 "outset" => Ok(Self::Outset),
173 "inset" => Ok(Self::Inset),
174 _ => Err(OxmlError::InvalidValue(format!("invalid ST_Border: {s}"))),
175 }
176 }
177
178 pub fn to_str(self) -> &'static str {
179 match self {
180 Self::None => "none",
181 Self::Single => "single",
182 Self::Thick => "thick",
183 Self::Double => "double",
184 Self::Dotted => "dotted",
185 Self::Dashed => "dashed",
186 Self::DotDash => "dotDash",
187 Self::DotDotDash => "dotDotDash",
188 Self::Triple => "triple",
189 Self::ThinThickSmallGap => "thinThickSmallGap",
190 Self::ThickThinSmallGap => "thickThinSmallGap",
191 Self::ThinThickMediumGap => "thinThickMediumGap",
192 Self::ThickThinMediumGap => "thickThinMediumGap",
193 Self::ThinThickLargeGap => "thinThickLargeGap",
194 Self::ThickThinLargeGap => "thickThinLargeGap",
195 Self::Wave => "wave",
196 Self::DoubleWave => "doubleWave",
197 Self::ThreeDEmboss => "threeDEmboss",
198 Self::ThreeDEngrave => "threeDEngrave",
199 Self::Outset => "outset",
200 Self::Inset => "inset",
201 }
202 }
203}
204
205#[derive(Debug, Clone, Copy, PartialEq, Eq)]
207pub enum ST_TabJc {
208 Left,
209 Center,
210 Right,
211 Decimal,
212 Bar,
213 Clear,
214 Num,
215}
216
217impl ST_TabJc {
218 pub fn from_str(s: &str) -> Result<Self> {
219 match s {
220 "left" | "start" => Ok(Self::Left),
221 "center" => Ok(Self::Center),
222 "right" | "end" => Ok(Self::Right),
223 "decimal" => Ok(Self::Decimal),
224 "bar" => Ok(Self::Bar),
225 "clear" => Ok(Self::Clear),
226 "num" => Ok(Self::Num),
227 _ => Err(OxmlError::InvalidValue(format!("invalid ST_TabJc: {s}"))),
228 }
229 }
230
231 pub fn to_str(self) -> &'static str {
232 match self {
233 Self::Left => "left",
234 Self::Center => "center",
235 Self::Right => "right",
236 Self::Decimal => "decimal",
237 Self::Bar => "bar",
238 Self::Clear => "clear",
239 Self::Num => "num",
240 }
241 }
242}
243
244#[derive(Debug, Clone, Copy, PartialEq, Eq)]
246pub enum ST_TabLeader {
247 None,
248 Dot,
249 Hyphen,
250 Underscore,
251 Heavy,
252 MiddleDot,
253}
254
255impl ST_TabLeader {
256 pub fn from_str(s: &str) -> Result<Self> {
257 match s {
258 "none" => Ok(Self::None),
259 "dot" => Ok(Self::Dot),
260 "hyphen" => Ok(Self::Hyphen),
261 "underscore" => Ok(Self::Underscore),
262 "heavy" => Ok(Self::Heavy),
263 "middleDot" => Ok(Self::MiddleDot),
264 _ => Err(OxmlError::InvalidValue(format!(
265 "invalid ST_TabLeader: {s}"
266 ))),
267 }
268 }
269
270 pub fn to_str(self) -> &'static str {
271 match self {
272 Self::None => "none",
273 Self::Dot => "dot",
274 Self::Hyphen => "hyphen",
275 Self::Underscore => "underscore",
276 Self::Heavy => "heavy",
277 Self::MiddleDot => "middleDot",
278 }
279 }
280}
281
282#[derive(Debug, Clone, Copy, PartialEq, Eq)]
284pub enum ST_SectionType {
285 NextPage,
286 Continuous,
287 EvenPage,
288 OddPage,
289 NextColumn,
290}
291
292impl ST_SectionType {
293 pub fn from_str(s: &str) -> Result<Self> {
294 match s {
295 "nextPage" => Ok(Self::NextPage),
296 "continuous" => Ok(Self::Continuous),
297 "evenPage" => Ok(Self::EvenPage),
298 "oddPage" => Ok(Self::OddPage),
299 "nextColumn" => Ok(Self::NextColumn),
300 _ => Err(OxmlError::InvalidValue(format!(
301 "invalid ST_SectionType: {s}"
302 ))),
303 }
304 }
305
306 pub fn to_str(self) -> &'static str {
307 match self {
308 Self::NextPage => "nextPage",
309 Self::Continuous => "continuous",
310 Self::EvenPage => "evenPage",
311 Self::OddPage => "oddPage",
312 Self::NextColumn => "nextColumn",
313 }
314 }
315}
316
317#[derive(Debug, Clone, Copy, PartialEq, Eq)]
319pub enum ST_PageOrientation {
320 Portrait,
321 Landscape,
322}
323
324impl ST_PageOrientation {
325 pub fn from_str(s: &str) -> Result<Self> {
326 match s {
327 "portrait" => Ok(Self::Portrait),
328 "landscape" => Ok(Self::Landscape),
329 _ => Err(OxmlError::InvalidValue(format!(
330 "invalid ST_PageOrientation: {s}"
331 ))),
332 }
333 }
334
335 pub fn to_str(self) -> &'static str {
336 match self {
337 Self::Portrait => "portrait",
338 Self::Landscape => "landscape",
339 }
340 }
341}
342
343#[derive(Debug, Clone, Copy, PartialEq, Eq)]
345pub enum ST_HighlightColor {
346 Black,
347 Blue,
348 Cyan,
349 DarkBlue,
350 DarkCyan,
351 DarkGray,
352 DarkGreen,
353 DarkMagenta,
354 DarkRed,
355 DarkYellow,
356 Green,
357 LightGray,
358 Magenta,
359 None,
360 Red,
361 White,
362 Yellow,
363}
364
365impl ST_HighlightColor {
366 pub fn from_str(s: &str) -> Result<Self> {
367 match s {
368 "black" => Ok(Self::Black),
369 "blue" => Ok(Self::Blue),
370 "cyan" => Ok(Self::Cyan),
371 "darkBlue" => Ok(Self::DarkBlue),
372 "darkCyan" => Ok(Self::DarkCyan),
373 "darkGray" => Ok(Self::DarkGray),
374 "darkGreen" => Ok(Self::DarkGreen),
375 "darkMagenta" => Ok(Self::DarkMagenta),
376 "darkRed" => Ok(Self::DarkRed),
377 "darkYellow" => Ok(Self::DarkYellow),
378 "green" => Ok(Self::Green),
379 "lightGray" => Ok(Self::LightGray),
380 "magenta" => Ok(Self::Magenta),
381 "none" => Ok(Self::None),
382 "red" => Ok(Self::Red),
383 "white" => Ok(Self::White),
384 "yellow" => Ok(Self::Yellow),
385 _ => Err(OxmlError::InvalidValue(format!(
386 "invalid ST_HighlightColor: {s}"
387 ))),
388 }
389 }
390
391 pub fn to_str(self) -> &'static str {
392 match self {
393 Self::Black => "black",
394 Self::Blue => "blue",
395 Self::Cyan => "cyan",
396 Self::DarkBlue => "darkBlue",
397 Self::DarkCyan => "darkCyan",
398 Self::DarkGray => "darkGray",
399 Self::DarkGreen => "darkGreen",
400 Self::DarkMagenta => "darkMagenta",
401 Self::DarkRed => "darkRed",
402 Self::DarkYellow => "darkYellow",
403 Self::Green => "green",
404 Self::LightGray => "lightGray",
405 Self::Magenta => "magenta",
406 Self::None => "none",
407 Self::Red => "red",
408 Self::White => "white",
409 Self::Yellow => "yellow",
410 }
411 }
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417
418 #[test]
421 fn kashida_justification_maps_to_both() {
422 for value in ["lowKashida", "mediumKashida", "highKashida"] {
423 assert_eq!(
424 ST_Jc::from_str(value).unwrap(),
425 ST_Jc::Both,
426 "{value} should justify"
427 );
428 }
429 }
430
431 #[test]
432 fn an_unknown_justification_is_still_rejected() {
433 assert!(ST_Jc::from_str("sideways").is_err());
435 assert!(ST_Jc::from_str("").is_err());
436 }
437
438 #[test]
439 fn a_document_using_kashida_justification_still_opens() {
440 use crate::document::CT_Document;
441
442 for value in ["lowKashida", "mediumKashida", "highKashida"] {
443 let xml = format!(
444 r#"<?xml version="1.0"?>
445<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
446 <w:body><w:p><w:pPr><w:jc w:val="{value}"/><w:keepNext/></w:pPr>
447 <w:r><w:t>Arabic justified text.</w:t></w:r></w:p></w:body></w:document>"#
448 );
449
450 let document = CT_Document::from_xml(xml.as_bytes())
451 .unwrap_or_else(|e| panic!("a document using {value} must open, got {e}"));
452
453 let crate::document::BodyContent::Paragraph(paragraph) = &document.body.content[0]
454 else {
455 panic!("expected a paragraph");
456 };
457 let properties = paragraph.properties.as_ref().expect("properties survive");
458 assert_eq!(properties.jc, Some(ST_Jc::Both), "{value} justifies");
459 assert_eq!(
460 properties.keep_next,
461 Some(true),
462 "{value} must not cost the paragraph its sibling properties"
463 );
464 assert_eq!(paragraph.text(), "Arabic justified text.");
465 }
466 }
467}