1use crate::data::PageRef;
22use crate::tree::clone::*;
23use crate::tree::{
24 Alignment, AnchorTarget, AttributeMap, ClearFloat, CodeBlock, Container, DateItem,
25 DefinitionListItem, Embed, FloatAlignment, ImageSource, LinkLabel, LinkLocation,
26 LinkType, ListItem, ListType, Module, PartialElement, Tab, Table, VariableMap,
27};
28use ref_map::*;
29use std::borrow::Cow;
30use std::num::NonZeroU32;
31
32#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
34#[serde(rename_all = "kebab-case", tag = "element", content = "data")]
35pub enum Element<'t> {
36 Container(Container<'t>),
40
41 Module(Module<'t>),
47
48 Text(Cow<'t, str>),
52
53 Raw(Cow<'t, str>),
59
60 Variable(Cow<'t, str>),
65
66 Email(Cow<'t, str>),
71
72 Table(Table<'t>),
74
75 TabView(Vec<Tab<'t>>),
77
78 Anchor {
83 target: Option<AnchorTarget>,
84 attributes: AttributeMap<'t>,
85 elements: Vec<Element<'t>>,
86 },
87
88 AnchorName(Cow<'t, str>),
93
94 Link {
103 #[serde(rename = "type")]
104 ltype: LinkType,
105 link: LinkLocation<'t>,
106 extra: Option<Cow<'t, str>>,
107 label: LinkLabel<'t>,
108 target: Option<AnchorTarget>,
109 },
110
111 Image {
117 source: ImageSource<'t>,
118 link: Option<LinkLocation<'t>>,
119 alignment: Option<FloatAlignment>,
120 attributes: AttributeMap<'t>,
121 },
122
123 List {
125 #[serde(rename = "type")]
126 ltype: ListType,
127 attributes: AttributeMap<'t>,
128 items: Vec<ListItem<'t>>,
129 },
130
131 DefinitionList(Vec<DefinitionListItem<'t>>),
133
134 RadioButton {
139 name: Cow<'t, str>,
140 checked: bool,
141 attributes: AttributeMap<'t>,
142 },
143
144 CheckBox {
148 checked: bool,
149 attributes: AttributeMap<'t>,
150 },
151
152 #[serde(rename_all = "kebab-case")]
158 Collapsible {
159 elements: Vec<Element<'t>>,
160 attributes: AttributeMap<'t>,
161 start_open: bool,
162 show_text: Option<Cow<'t, str>>,
163 hide_text: Option<Cow<'t, str>>,
164 show_top: bool,
165 show_bottom: bool,
166 },
167
168 TableOfContents {
172 attributes: AttributeMap<'t>,
173 align: Option<Alignment>,
174 },
175
176 Footnote,
184
185 FootnoteBlock {
190 title: Option<Cow<'t, str>>,
191 hide: bool,
192 },
193
194 BibliographyCite { label: Cow<'t, str>, brackets: bool },
199
200 BibliographyBlock {
204 index: usize,
205 title: Option<Cow<'t, str>>,
206 hide: bool,
207 },
208
209 #[serde(rename_all = "kebab-case")]
211 User {
212 name: Cow<'t, str>,
213 show_avatar: bool,
214 },
215
216 Date {
218 value: DateItem,
219 format: Option<Cow<'t, str>>,
220 hover: bool,
221 },
222
223 Color {
227 color: Cow<'t, str>,
228 elements: Vec<Element<'t>>,
229 },
230
231 Code(CodeBlock<'t>),
233
234 #[serde(rename_all = "kebab-case")]
236 Math {
237 name: Option<Cow<'t, str>>,
238 latex_source: Cow<'t, str>,
239 },
240
241 #[serde(rename_all = "kebab-case")]
243 MathInline { latex_source: Cow<'t, str> },
244
245 EquationReference(Cow<'t, str>),
247
248 Embed(Embed<'t>),
250
251 Html {
253 contents: Cow<'t, str>,
254 attributes: AttributeMap<'t>,
255 },
256
257 Iframe {
259 url: Cow<'t, str>,
260 attributes: AttributeMap<'t>,
261 },
262
263 #[serde(rename_all = "kebab-case")]
267 Include {
268 paragraph_safe: bool,
269 variables: VariableMap<'t>,
270 location: PageRef,
271 elements: Vec<Element<'t>>,
272 },
273
274 Style(Cow<'t, str>),
278
279 LineBreak,
283
284 LineBreaks(NonZeroU32),
286
287 ClearFloat(ClearFloat),
289
290 HorizontalRule,
292
293 Partial(PartialElement<'t>),
300}
301
302impl Element<'_> {
303 pub fn is_whitespace(&self) -> bool {
312 match self {
313 Element::LineBreak => true,
314 Element::Text(string) if string.chars().all(|c| c.is_whitespace()) => true,
315 _ => false,
316 }
317 }
318
319 pub fn name(&self) -> &'static str {
321 match self {
322 Element::Container(container) => container.ctype().name(),
323 Element::Module(module) => module.name(),
324 Element::Text(_) => "Text",
325 Element::Raw(_) => "Raw",
326 Element::Variable(_) => "Variable",
327 Element::Email(_) => "Email",
328 Element::Table(_) => "Table",
329 Element::TabView(_) => "TabView",
330 Element::Anchor { .. } => "Anchor",
331 Element::AnchorName(_) => "AnchorName",
332 Element::Link { .. } => "Link",
333 Element::Image { .. } => "Image",
334 Element::List { .. } => "List",
335 Element::DefinitionList(_) => "DefinitionList",
336 Element::RadioButton { .. } => "RadioButton",
337 Element::CheckBox { .. } => "CheckBox",
338 Element::Collapsible { .. } => "Collapsible",
339 Element::TableOfContents { .. } => "TableOfContents",
340 Element::Footnote => "Footnote",
341 Element::FootnoteBlock { .. } => "FootnoteBlock",
342 Element::BibliographyCite { .. } => "BibliographyCite",
343 Element::BibliographyBlock { .. } => "BibliographyBlock",
344 Element::User { .. } => "User",
345 Element::Date { .. } => "Date",
346 Element::Color { .. } => "Color",
347 Element::Code { .. } => "Code",
348 Element::Math { .. } => "Math",
349 Element::MathInline { .. } => "MathInline",
350 Element::EquationReference(_) => "EquationReference",
351 Element::Embed(_) => "Embed",
352 Element::Html { .. } => "HTML",
353 Element::Iframe { .. } => "Iframe",
354 Element::Include { .. } => "Include",
355 Element::Style(_) => "Style",
356 Element::LineBreak => "LineBreak",
357 Element::LineBreaks { .. } => "LineBreaks",
358 Element::ClearFloat(_) => "ClearFloat",
359 Element::HorizontalRule => "HorizontalRule",
360 Element::Partial(partial) => partial.name(),
361 }
362 }
363
364 pub fn paragraph_safe(&self) -> bool {
374 match self {
375 Element::Container(container) => container.ctype().paragraph_safe(),
376 Element::Module(_) => false,
377 Element::Text(_)
378 | Element::Raw(_)
379 | Element::Variable(_)
380 | Element::Email(_) => true,
381 Element::Table(_) => false,
382 Element::TabView(_) => false,
383 Element::Anchor { .. } | Element::AnchorName(_) | Element::Link { .. } => {
384 true
385 }
386 Element::Image { .. } => true,
387 Element::List { .. } => false,
388 Element::DefinitionList(_) => false,
389 Element::RadioButton { .. } | Element::CheckBox { .. } => true,
390 Element::Collapsible { .. } => false,
391 Element::TableOfContents { .. } => false,
392 Element::Footnote => true,
393 Element::FootnoteBlock { .. } => false,
394 Element::BibliographyCite { .. } => true,
395 Element::BibliographyBlock { .. } => false,
396 Element::User { .. } => true,
397 Element::Date { .. } => true,
398 Element::Color { .. } => true,
399 Element::Code { .. } => false,
400 Element::Math { .. } => false,
401 Element::MathInline { .. } => true,
402 Element::EquationReference(_) => true,
403 Element::Embed(_) => false,
404 Element::Html { .. } | Element::Iframe { .. } => false,
405 Element::Include { paragraph_safe, .. } => *paragraph_safe,
406 Element::Style(_) => false,
407 Element::LineBreak | Element::LineBreaks { .. } => true,
408 Element::ClearFloat(_) => false,
409 Element::HorizontalRule => false,
410 Element::Partial(_) => {
411 panic!("Should not check for paragraph safety of partials")
412 }
413 }
414 }
415
416 pub fn to_owned(&self) -> Element<'static> {
422 match self {
423 Element::Container(container) => Element::Container(container.to_owned()),
424 Element::Module(module) => Element::Module(module.to_owned()),
425 Element::Text(text) => Element::Text(string_to_owned(text)),
426 Element::Raw(text) => Element::Raw(string_to_owned(text)),
427 Element::Variable(name) => Element::Variable(string_to_owned(name)),
428 Element::Email(email) => Element::Email(string_to_owned(email)),
429 Element::Table(table) => Element::Table(table.to_owned()),
430 Element::TabView(tabs) => {
431 Element::TabView(tabs.iter().map(|tab| tab.to_owned()).collect())
432 }
433 Element::Anchor {
434 target,
435 attributes,
436 elements,
437 } => Element::Anchor {
438 target: *target,
439 attributes: attributes.to_owned(),
440 elements: elements_to_owned(elements),
441 },
442 Element::AnchorName(name) => Element::AnchorName(string_to_owned(name)),
443 Element::Link {
444 ltype,
445 link,
446 extra,
447 label,
448 target,
449 } => Element::Link {
450 ltype: *ltype,
451 link: link.to_owned(),
452 extra: option_string_to_owned(extra),
453 label: label.to_owned(),
454 target: *target,
455 },
456 Element::List {
457 ltype,
458 attributes,
459 items,
460 } => Element::List {
461 ltype: *ltype,
462 attributes: attributes.to_owned(),
463 items: list_items_to_owned(items),
464 },
465 Element::Image {
466 source,
467 link,
468 alignment,
469 attributes,
470 } => Element::Image {
471 source: source.to_owned(),
472 link: link.ref_map(|link| link.to_owned()),
473 alignment: *alignment,
474 attributes: attributes.to_owned(),
475 },
476 Element::DefinitionList(items) => Element::DefinitionList(
477 items.iter().map(|item| item.to_owned()).collect(),
478 ),
479 Element::RadioButton {
480 name,
481 checked,
482 attributes,
483 } => Element::RadioButton {
484 name: string_to_owned(name),
485 checked: *checked,
486 attributes: attributes.to_owned(),
487 },
488 Element::CheckBox {
489 checked,
490 attributes,
491 } => Element::CheckBox {
492 checked: *checked,
493 attributes: attributes.to_owned(),
494 },
495 Element::Collapsible {
496 elements,
497 attributes,
498 start_open,
499 show_text,
500 hide_text,
501 show_top,
502 show_bottom,
503 } => Element::Collapsible {
504 elements: elements_to_owned(elements),
505 attributes: attributes.to_owned(),
506 start_open: *start_open,
507 show_text: option_string_to_owned(show_text),
508 hide_text: option_string_to_owned(hide_text),
509 show_top: *show_top,
510 show_bottom: *show_bottom,
511 },
512 Element::TableOfContents { align, attributes } => Element::TableOfContents {
513 align: *align,
514 attributes: attributes.to_owned(),
515 },
516 Element::Footnote => Element::Footnote,
517 Element::FootnoteBlock { title, hide } => Element::FootnoteBlock {
518 title: option_string_to_owned(title),
519 hide: *hide,
520 },
521 Element::BibliographyCite { label, brackets } => Element::BibliographyCite {
522 label: string_to_owned(label),
523 brackets: *brackets,
524 },
525 Element::BibliographyBlock { index, title, hide } => {
526 Element::BibliographyBlock {
527 index: *index,
528 title: option_string_to_owned(title),
529 hide: *hide,
530 }
531 }
532 Element::User { name, show_avatar } => Element::User {
533 name: string_to_owned(name),
534 show_avatar: *show_avatar,
535 },
536 Element::Date {
537 value,
538 format,
539 hover,
540 } => Element::Date {
541 value: *value,
542 format: option_string_to_owned(format),
543 hover: *hover,
544 },
545 Element::Color { color, elements } => Element::Color {
546 color: string_to_owned(color),
547 elements: elements_to_owned(elements),
548 },
549 Element::Code(code_block) => Element::Code(code_block.to_owned()),
550 Element::Math { name, latex_source } => Element::Math {
551 name: option_string_to_owned(name),
552 latex_source: string_to_owned(latex_source),
553 },
554 Element::MathInline { latex_source } => Element::MathInline {
555 latex_source: string_to_owned(latex_source),
556 },
557 Element::EquationReference(name) => {
558 Element::EquationReference(string_to_owned(name))
559 }
560 Element::Embed(embed) => Element::Embed(embed.to_owned()),
561 Element::Html {
562 contents,
563 attributes,
564 } => Element::Html {
565 contents: string_to_owned(contents),
566 attributes: attributes.to_owned(),
567 },
568 Element::Iframe { url, attributes } => Element::Iframe {
569 url: string_to_owned(url),
570 attributes: attributes.to_owned(),
571 },
572 Element::Include {
573 paragraph_safe,
574 variables,
575 location,
576 elements,
577 } => Element::Include {
578 paragraph_safe: *paragraph_safe,
579 variables: string_map_to_owned(variables),
580 location: location.to_owned(),
581 elements: elements_to_owned(elements),
582 },
583 Element::Style(css) => Element::Style(string_to_owned(css)),
584 Element::LineBreak => Element::LineBreak,
585 Element::LineBreaks(amount) => Element::LineBreaks(*amount),
586 Element::ClearFloat(clear_float) => Element::ClearFloat(*clear_float),
587 Element::HorizontalRule => Element::HorizontalRule,
588 Element::Partial(partial) => Element::Partial(partial.to_owned()),
589 }
590 }
591}