1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// Copyright 2020 Adobe. All rights reserved.
// This file is licensed to you under the Apache License,
// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
// or the MIT license (http://opensource.org/licenses/MIT),
// at your option.

// Unless required by applicable law or agreed to in writing,
// this software is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
// specific language governing permissions and limitations under
// each license.

use std::ffi::{CStr, CString};

use crate::ffi;
use crate::xmp_date_time::XmpDateTime;

/// The `XmpMeta` struct allows access to the XMP Toolkit core services.
///
/// You can create `XmpMeta` structs from metadata that you construct,
/// or that you obtain from files using the XMP Toolkit's `XmpFile` struct.
pub struct XmpMeta {
    pub(crate) m: *mut ffi::CXmpMeta,
    // pub(crate) is used because XmpFile::xmp
    // can create this struct.
}

impl Drop for XmpMeta {
    fn drop(&mut self) {
        unsafe {
            ffi::CXmpMetaDrop(self.m);
        }
    }
}

impl Default for XmpMeta {
    fn default() -> Self {
        XmpMeta::new()
    }
}

impl XmpMeta {
    /// Creates a new, empty metadata struct.
    pub fn new() -> XmpMeta {
        let m = unsafe { ffi::CXmpMetaNew() };
        XmpMeta { m }
    }

    /// Registers a namespace URI with a suggested prefix.
    ///
    /// If the URI is not registered but the suggested prefix
    /// is in use, a unique prefix is created from the suggested one.
    /// The actual registered prefix is returned. It is not an error
    /// if the URI is already registered, regardless of the prefix.
    ///
    /// ## Arguments
    ///
    /// * `namespace_uri`: The URI for the namespace. Must be a
    /// valid XML URI.
    ///
    /// * `suggested_prefix`: The suggested prefix to be used if
    /// the URI is not yet registered. Must be a valid XML name.
    ///
    /// Returns the prefix actually registered for this URI.
    ///
    /// **NOTE:** No checking is done on either the URI or the prefix.
    pub fn register_namespace(namespace_uri: &str, suggested_prefix: &str) -> String {
        // These .unwrap() calls are deemed unlikely to panic as this
        // function is typically called with known, standardized strings
        // in the ASCII space.
        let c_ns = CString::new(namespace_uri).unwrap();
        let c_sp = CString::new(suggested_prefix).unwrap();

        unsafe {
            let c_result = ffi::CXmpMetaRegisterNamespace(c_ns.as_ptr(), c_sp.as_ptr());
            CStr::from_ptr(c_result).to_string_lossy().into_owned()
        }
    }

    /// Gets a property value.
    ///
    /// When specifying a namespace and path (in this and all other accessors):
    /// * If a namespace URI is specified, it must be for a registered namespace.
    /// * If the namespace is specified only by a prefix in the property name path,
    /// it must be a registered prefix.
    /// * If both a URI and path prefix are present, they must be corresponding
    /// parts of a registered namespace.
    ///
    /// ## Arguments
    ///
    /// * `schema_ns`: The namespace URI for the property. The URI must be for
    /// a registered namespace. Must not be an empty string.
    ///
    /// * `prop_name`: The name of the property. Can be a general path expression.
    /// Must not be an empty string. The first component can be a namespace prefix;
    /// if present without a `schema_ns` value, the prefix specifies the namespace.
    /// The prefix must be for a registered namespace, and if a namespace URI is
    /// specified, must match the registered prefix for that namespace.
    pub fn property(&self, schema_ns: &str, prop_name: &str) -> Option<String> {
        let c_ns = CString::new(schema_ns).unwrap();
        let c_name = CString::new(prop_name).unwrap();

        unsafe {
            let c_result = ffi::CXmpMetaGetProperty(self.m, c_ns.as_ptr(), c_name.as_ptr());

            if c_result.is_null() {
                None
            } else {
                Some(CStr::from_ptr(c_result).to_string_lossy().into_owned())
            }
        }
    }

    /// Creates or sets a property value.
    ///
    /// This is the simplest property setter. Use it for top-level
    /// simple properties.
    ///
    /// ## Arguments
    ///
    /// * `schema_ns`: The namespace URI; see `property()`.
    ///
    /// * `prop_name`: The name of the property. Can be a general
    /// path expression. Must not be an empty string. See `property`
    /// for namespace prefix usage.
    ///
    /// * `prop_value`: The new value.
    pub fn set_property(&mut self, schema_ns: &str, prop_name: &str, prop_value: &str) {
        let c_ns = CString::new(schema_ns).unwrap();
        let c_name = CString::new(prop_name).unwrap();
        let c_value = CString::new(prop_value).unwrap();

        unsafe {
            ffi::CXmpMetaSetProperty(self.m, c_ns.as_ptr(), c_name.as_ptr(), c_value.as_ptr());
        }
    }

    /// Creates or sets a property value using an `XmpDateTeim` structure.
    ///
    /// This is the simplest property setter. Use it for top-level
    /// simple properties.
    ///
    /// ## Arguments
    ///
    /// * `schema_ns`: The namespace URI; see `property()`.
    ///
    /// * `prop_name`: The name of the property. Can be a general
    /// path expression. Must not be an empty string. See `property`
    /// for namespace prefix usage.
    ///
    /// * `prop_value`: The new value.
    pub fn set_property_date(
        &mut self,
        schema_ns: &str,
        prop_name: &str,
        prop_value: &XmpDateTime,
    ) {
        let c_ns = CString::new(schema_ns).unwrap();
        let c_name = CString::new(prop_name).unwrap();

        unsafe {
            ffi::CXmpMetaSetPropertyDate(self.m, c_ns.as_ptr(), c_name.as_ptr(), prop_value.dt);
        }
    }

    /// Rreports whether a property currently exists.
    ///
    /// ## Arguments
    ///
    /// * `schema_ns`: The namespace URI; see `property()`.
    ///
    /// * `prop_name`: The name of the property. Can be a general
    /// path expression. Must not be an empty string. See `property`
    /// for namespace prefix usage.
    pub fn does_property_exist(&self, schema_ns: &str, prop_name: &str) -> bool {
        let c_ns = CString::new(schema_ns).unwrap();
        let c_name = CString::new(prop_name).unwrap();

        let r = unsafe { ffi::CXmpMetaDoesPropertyExist(self.m, c_ns.as_ptr(), c_name.as_ptr()) };
        r != 0
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn new_empty() {
        let mut _m = XmpMeta::new();
    }

    #[test]
    fn register_namespace() {
        assert_eq!(
            XmpMeta::register_namespace("http://purl.org/dc/terms/", "dcterms"),
            "dcterms:"
        );
    }
}