1#![doc = include_str!("../README.md")]
2
3mod parsing;
4mod predefined;
5
6use std::collections::BTreeMap;
7use std::sync::Arc;
8
9use gufo_common::xmp::Namespace;
10use xml::name::OwnedName;
11
12#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
13pub struct Tag {
14 namespace: Namespace,
15 name: String,
16}
17
18impl Tag {
19 pub fn new(namespace: Namespace, name: String) -> Self {
20 Self { namespace, name }
21 }
22
23 fn from_name(name: &OwnedName) -> Option<Self> {
24 if let Some(namespace_url) = &name.namespace {
25 let namespace = Namespace::from_url(namespace_url);
26
27 let name = name.local_name.to_owned();
28 Some(Self::new(namespace, name))
29 } else {
30 None
31 }
32 }
33}
34
35impl<T: gufo_common::xmp::Field> From<T> for Tag {
36 fn from(_: T) -> Self {
37 Self {
38 name: T::NAME.to_string(),
39 namespace: T::NAMESPACE,
40 }
41 }
42}
43
44#[derive(Debug, Clone)]
45pub struct Xmp {
46 inner: Vec<u8>,
47 entries: BTreeMap<Tag, String>,
48}
49
50#[derive(Debug, Clone, thiserror::Error)]
51#[non_exhaustive]
52pub enum Error {
53 #[error("XmlReader: {0}")]
54 XmlReader(xml::reader::Error),
55 #[error("XmlWriter: {0}")]
56 XmlWriter(Arc<xml::writer::Error>),
57}
58
59impl From<xml::reader::Error> for Error {
60 fn from(value: xml::reader::Error) -> Self {
61 Self::XmlReader(value)
62 }
63}
64
65impl From<xml::writer::Error> for Error {
66 fn from(value: xml::writer::Error) -> Self {
67 Self::XmlWriter(Arc::new(value))
68 }
69}
70
71impl Xmp {
72 pub fn new(data: Vec<u8>) -> Result<Self, Error> {
73 let entries = Self::lookup(&data)?;
74
75 Ok(Self {
76 inner: data,
77 entries,
78 })
79 }
80
81 pub fn update(&mut self, updates: BTreeMap<Tag, String>) -> Result<(), Error> {
82 let (entries, data) = Self::lookup_and_update(&self.inner, updates)?;
83 self.entries = entries;
84 self.inner = data;
85
86 Ok(())
87 }
88
89 pub fn get(&self, tag: impl Into<Tag>) -> Option<&str> {
90 self.entries.get(&tag.into()).map(|x| x.as_str())
91 }
92
93 pub fn get_frac(&self, tag: impl Into<Tag>) -> Option<(u32, u32)> {
94 let (x, y) = self.get(tag)?.split_once('/')?;
95 let x = x.parse().ok()?;
96 let y = y.parse().ok()?;
97
98 Some((x, y))
99 }
100
101 pub fn get_frac_f32(&self, tag: impl Into<Tag>) -> Option<f32> {
102 let (x, y) = self.get_frac(tag)?;
103
104 let res = x as f32 / y as f32;
105 if res.is_finite() {
106 Some(res)
107 } else {
108 None
109 }
110 }
111
112 pub fn get_u16(&self, tag: impl Into<Tag>) -> Option<u16> {
113 self.get(tag)?.parse().ok()
114 }
115
116 #[cfg(feature = "chrono")]
117 pub fn get_date_time(&self, tag: impl Into<Tag>) -> Option<gufo_common::datetime::DateTime> {
118 Some(gufo_common::datetime::DateTime::FixedOffset(
119 self.get(tag)
120 .and_then(|x| chrono::DateTime::parse_from_rfc3339(x).ok())?,
121 ))
122 }
123
124 pub fn entries(&self) -> &BTreeMap<Tag, String> {
125 &self.entries
126 }
127
128 pub fn into_inner(self) -> Vec<u8> {
129 self.inner
130 }
131}