odox_core/meta.rs
1//! What a document says about itself: `meta.xml`.
2//
3// Author: David M. Anderson
4// Built with AI assistance (Claude, Anthropic)
5
6use crate::xml::{Element, Ns};
7
8/// A document's metadata, as far as a window shows it.
9///
10/// Every field is optional because every field is optional in the format. The
11/// whole part is optional too, and a document with no `meta.xml` gives back the
12/// default of this.
13#[derive(Debug, Clone, Default, PartialEq, Eq)]
14pub struct Meta {
15 /// `dc:title`, which a window shows in preference to the file name when the
16 /// document carries one.
17 pub title: Option<String>,
18 /// `dc:subject`.
19 pub subject: Option<String>,
20 /// `dc:description`.
21 pub description: Option<String>,
22 /// `meta:initial-creator`, who made the document.
23 pub initial_creator: Option<String>,
24 /// `dc:creator`, who last changed it. ODF uses the Dublin Core term for the
25 /// last editor rather than the author, which is the opposite of what the
26 /// word suggests.
27 pub last_editor: Option<String>,
28 /// `meta:creation-date`, as written: an ISO 8601 timestamp, kept as text
29 /// because nothing here does arithmetic on it and a date library is a
30 /// dependency this crate would otherwise not have.
31 pub created: Option<String>,
32 /// `dc:date`, when it was last changed.
33 pub modified: Option<String>,
34 /// `meta:generator`, the application that wrote it.
35 pub generator: Option<String>,
36 /// Every `meta:keyword`, in order.
37 pub keywords: Vec<String>,
38}
39
40impl Meta {
41 /// Read the metadata out of a parsed `meta.xml`.
42 pub fn read(root: &Element) -> Self {
43 let mut meta = Self::default();
44 let Some(body) = root.child(&Ns::Office, "meta") else {
45 return meta;
46 };
47 for element in body.elements() {
48 let text = element.plain_text();
49 let slot = match () {
50 () if element.is(&Ns::Dc, "title") => &mut meta.title,
51 () if element.is(&Ns::Dc, "subject") => &mut meta.subject,
52 () if element.is(&Ns::Dc, "description") => &mut meta.description,
53 () if element.is(&Ns::Meta, "initial-creator") => &mut meta.initial_creator,
54 () if element.is(&Ns::Dc, "creator") => &mut meta.last_editor,
55 () if element.is(&Ns::Meta, "creation-date") => &mut meta.created,
56 () if element.is(&Ns::Dc, "date") => &mut meta.modified,
57 () if element.is(&Ns::Meta, "generator") => &mut meta.generator,
58 () if element.is(&Ns::Meta, "keyword") => {
59 if !text.is_empty() {
60 meta.keywords.push(text);
61 }
62 continue;
63 }
64 () => continue,
65 };
66 if !text.is_empty() {
67 *slot = Some(text);
68 }
69 }
70 meta
71 }
72}