Skip to main content

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}