docx_reader/documents/elements/
instr_toc.rs

1use serde::Serialize;
2
3use crate::documents::*;
4
5#[derive(Serialize, Debug, Clone, PartialEq, Default)]
6pub struct StyleWithLevel(pub (String, usize));
7
8impl StyleWithLevel {
9	pub fn new(s: impl Into<String>, l: usize) -> Self {
10		Self((s.into(), l))
11	}
12}
13// https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_TOCTOC_topic_ID0ELZO1.html
14#[derive(Serialize, Debug, Clone, PartialEq, Default)]
15#[serde(rename_all = "camelCase")]
16pub struct InstrToC {
17	// \o If no heading range is specified, all heading levels used in the document are listed.
18	#[serde(skip_serializing_if = "Option::is_none")]
19	pub heading_styles_range: Option<(usize, usize)>,
20	// \l Includes TC fields that assign entries to one of the levels specified by text in this switch's field-argument as a range having the form startLevel-endLevel,
21	//    where startLevel and endLevel are integers, and startLevel has a value equal-to or less-than endLevel.
22	//    TC fields that assign entries to lower levels are skipped.
23	#[serde(skip_serializing_if = "Option::is_none")]
24	pub tc_field_level_range: Option<(usize, usize)>,
25	// \n Without field-argument, omits page numbers from the table of contents.
26	// .Page numbers are omitted from all levels unless a range of entry levels is specified by text in this switch's field-argument.
27	// A range is specified as for \l.
28	#[serde(skip_serializing_if = "Option::is_none")]
29	pub omit_page_numbers_level_range: Option<(usize, usize)>,
30	// \b includes entries only from the portion of the document marked by the bookmark named by text in this switch's field-argument.
31	#[serde(skip_serializing_if = "Option::is_none")]
32	pub entry_bookmark_name: Option<String>,
33	// \t Uses paragraphs formatted with styles other than the built-in heading styles.
34	// .  text in this switch's field-argument specifies those styles as a set of comma-separated doublets,
35	//    with each doublet being a comma-separated set of style name and table of content level. \t can be combined with \o.
36	pub styles_with_levels: Vec<StyleWithLevel>,
37	//  struct S texWin Lis switch's field-argument specifies a sequence of character
38	// .  The default is a tab with leader dots.
39	#[serde(skip_serializing_if = "Option::is_none")]
40	pub entry_and_page_number_separator: Option<String>,
41	// \d
42	#[serde(skip_serializing_if = "Option::is_none")]
43	pub sequence_and_page_numbers_separator: Option<String>,
44	// \a
45	pub caption_label: Option<String>,
46	// \c
47	#[serde(skip_serializing_if = "Option::is_none")]
48	pub caption_label_including_numbers: Option<String>,
49	// \s
50	#[serde(skip_serializing_if = "Option::is_none")]
51	pub seq_field_identifier_for_prefix: Option<String>,
52	// \f
53	#[serde(skip_serializing_if = "Option::is_none")]
54	pub tc_field_identifier: Option<String>,
55	// \h
56	pub hyperlink: bool,
57	// \w
58	pub preserve_tab: bool,
59	// \x
60	pub preserve_new_line: bool,
61	// \u
62	pub use_applied_paragraph_line_level: bool,
63	// \z Hides tab leader and page numbers in Web layout view.
64	pub hide_tab_and_page_numbers_in_webview: bool,
65}
66
67impl InstrToC {
68	pub fn new() -> Self {
69		Self::default()
70	}
71
72	pub fn with_instr_text(s: &str) -> Self {
73		Self::from_str(s).expect("should convert to InstrToC")
74	}
75
76	pub fn heading_styles_range(mut self, start: usize, end: usize) -> Self {
77		self.heading_styles_range = Some((start, end));
78		self
79	}
80
81	pub fn tc_field_level_range(mut self, start: usize, end: usize) -> Self {
82		self.tc_field_level_range = Some((start, end));
83		self
84	}
85
86	pub fn tc_field_identifier(mut self, t: impl Into<String>) -> Self {
87		self.tc_field_identifier = Some(t.into());
88		self
89	}
90
91	pub fn omit_page_numbers_level_range(mut self, start: usize, end: usize) -> Self {
92		self.omit_page_numbers_level_range = Some((start, end));
93		self
94	}
95
96	pub fn entry_and_page_number_separator(mut self, t: impl Into<String>) -> Self {
97		self.entry_and_page_number_separator = Some(t.into());
98		self
99	}
100
101	pub fn entry_bookmark_name(mut self, t: impl Into<String>) -> Self {
102		self.entry_bookmark_name = Some(t.into());
103		self
104	}
105
106	pub fn caption_label(mut self, t: impl Into<String>) -> Self {
107		self.caption_label = Some(t.into());
108		self
109	}
110
111	pub fn caption_label_including_numbers(mut self, t: impl Into<String>) -> Self {
112		self.caption_label_including_numbers = Some(t.into());
113		self
114	}
115
116	pub fn sequence_and_page_numbers_separator(mut self, t: impl Into<String>) -> Self {
117		self.sequence_and_page_numbers_separator = Some(t.into());
118		self
119	}
120
121	pub fn seq_field_identifier_for_prefix(mut self, t: impl Into<String>) -> Self {
122		self.seq_field_identifier_for_prefix = Some(t.into());
123		self
124	}
125
126	pub fn hyperlink(mut self) -> Self {
127		self.hyperlink = true;
128		self
129	}
130
131	pub fn preserve_tab(mut self) -> Self {
132		self.preserve_tab = true;
133		self
134	}
135
136	pub fn preserve_new_line(mut self) -> Self {
137		self.preserve_new_line = true;
138		self
139	}
140
141	pub fn use_applied_paragraph_line_level(mut self) -> Self {
142		self.use_applied_paragraph_line_level = true;
143		self
144	}
145
146	pub fn hide_tab_and_page_numbers_in_webview(mut self) -> Self {
147		self.hide_tab_and_page_numbers_in_webview = true;
148		self
149	}
150
151	pub fn add_style_with_level(mut self, s: StyleWithLevel) -> Self {
152		self.styles_with_levels.push(s);
153		self
154	}
155}
156
157fn parse_level_range(i: &str) -> Option<(usize, usize)> {
158	let r = i.replace("&quot;", "").replace("\"", "");
159	let r: Vec<&str> = r.split('-').collect();
160	if let Some(s) = r.get(0) {
161		if let Ok(s) = usize::from_str(s) {
162			if let Some(e) = r.get(1) {
163				if let Ok(e) = usize::from_str(e) {
164					return Some((s, e));
165				}
166			}
167		}
168	}
169	None
170}
171
172impl std::str::FromStr for InstrToC {
173	type Err = ();
174
175	fn from_str(instr: &str) -> Result<Self, Self::Err> {
176		let mut s = instr.split(' ');
177		let mut toc = InstrToC::new();
178		loop {
179			if let Some(i) = s.next() {
180				match i {
181					"\\a" => {
182						if let Some(r) = s.next() {
183							let r = r.replace("&quot;", "").replace("\"", "");
184							toc = toc.caption_label(r);
185						}
186					}
187					"\\b" => {
188						if let Some(r) = s.next() {
189							let r = r.replace("&quot;", "").replace("\"", "");
190							toc = toc.entry_bookmark_name(r);
191						}
192					}
193					"\\c" => {
194						if let Some(r) = s.next() {
195							let r = r.replace("&quot;", "").replace("\"", "");
196							toc = toc.caption_label_including_numbers(r);
197						}
198					}
199					"\\d" => {
200						if let Some(r) = s.next() {
201							let r = r.replace("&quot;", "").replace("\"", "");
202							toc = toc.sequence_and_page_numbers_separator(r);
203						}
204					}
205					"\\f" => {
206						if let Some(r) = s.next() {
207							let r = r.replace("&quot;", "").replace("\"", "");
208							toc = toc.tc_field_identifier(r);
209						}
210					}
211					"\\h" => toc = toc.hyperlink(),
212					"\\l" => {
213						if let Some(r) = s.next() {
214							if let Some((s, e)) = parse_level_range(r) {
215								toc = toc.tc_field_level_range(s, e);
216							}
217						}
218					}
219					"\\n" => {
220						if let Some(r) = s.next() {
221							if let Some((s, e)) = parse_level_range(r) {
222								toc = toc.omit_page_numbers_level_range(s, e);
223							}
224						}
225					}
226					"\\o" => {
227						if let Some(r) = s.next() {
228							if let Some((s, e)) = parse_level_range(r) {
229								toc = toc.heading_styles_range(s, e);
230							}
231						}
232					}
233					"\\p" => {
234						if let Some(r) = s.next() {
235							let r = r.replace("&quot;", "").replace("\"", "");
236							toc = toc.entry_and_page_number_separator(r);
237						}
238					}
239					"\\s" => {
240						if let Some(r) = s.next() {
241							let r = r.replace("&quot;", "").replace("\"", "");
242							toc = toc.seq_field_identifier_for_prefix(r);
243						}
244					}
245					"\\t" => {
246						if let Some(r) = s.next() {
247							let r = r.replace("&quot;", "").replace("\"", "");
248							let mut r = r.split(',');
249							loop {
250								if let Some(style) = r.next() {
251									if let Some(level) = r.next() {
252										if let Ok(level) = usize::from_str(level) {
253											toc = toc.add_style_with_level(StyleWithLevel((
254												style.to_string(),
255												level,
256											)));
257											continue;
258										}
259									}
260								}
261								break;
262							}
263						}
264					}
265					"\\u" => toc = toc.use_applied_paragraph_line_level(),
266					"\\w" => toc = toc.preserve_tab(),
267					"\\x" => toc = toc.preserve_new_line(),
268					"\\z" => toc = toc.hide_tab_and_page_numbers_in_webview(),
269					_ => {}
270				}
271			} else {
272				return Ok(toc);
273			}
274		}
275	}
276}