Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new API XmpIter::array_property #78

Merged
merged 9 commits into from
Oct 11, 2022
32 changes: 31 additions & 1 deletion src/ffi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,6 @@ extern "C" {
return NULL;
}


const char* CXmpMetaRegisterNamespace(CXmpError* outError,
const char* namespaceURI,
const char* suggestedPrefix) {
Expand Down Expand Up @@ -401,6 +400,37 @@ extern "C" {
#endif
}

const char* CXmpMetaGetArrayItem(CXmpMeta* m,
CXmpError* outError,
const char* schemaNS,
const char* propName,
AdobeXMPCommon::uint32 index,
AdobeXMPCommon::uint32* outOptions) {
#ifdef NOOP_FFI
*outOptions = 0;
return NULL;
#else
std::string propValue;

try {
if (m->m.GetArrayItem(schemaNS, propName, index, &propValue, outOptions)) {
return copyStringForResult(propValue);
} else {
*outOptions = 0;
return NULL;
}
}
catch (XMP_Error& e) {
copyErrorForResult(e, outError);
return NULL;
}
catch (...) {
signalUnknownError(outError);
return NULL;
}
#endif
}

// --- CXmpDateTime ---

CXmpDateTime* CXmpDateTimeNew() {
Expand Down
9 changes: 9 additions & 0 deletions src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ extern "C" {
prop_value: *const CXmpDateTime,
);

pub(crate) fn CXmpMetaGetArrayItem(
meta: *mut CXmpMeta,
out_error: *mut CXmpError,
schema_ns: *const c_char,
prop_name: *const c_char,
index: u32,
out_options: *mut u32,
) -> *mut c_char;

// --- CXmpDateTime ---

pub(crate) fn CXmpDateTimeNew() -> *mut CXmpDateTime;
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub mod xmp_ns;
pub use xmp_date_time::XmpDateTime;
pub use xmp_error::{XmpError, XmpErrorType, XmpResult};
pub use xmp_file::{OpenFileOptions, XmpFile};
pub use xmp_meta::XmpMeta;
pub use xmp_meta::{ArrayProperty, XmpMeta, XmpValue};

#[cfg(test)]
mod tests;
50 changes: 50 additions & 0 deletions src/tests/xmp_meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,3 +313,53 @@ mod set_property_date {
);
}
}

mod array_property {
use std::str::FromStr;

use crate::{tests::fixtures::*, XmpMeta, XmpValue};

#[test]
fn happy_path_creator_seq() {
let m = XmpMeta::from_str(PURPLE_SQUARE_XMP).unwrap();

let mut creators: Vec<XmpValue> = m
.array_property("http://purl.org/dc/elements/1.1/", "creator")
.collect();

assert_eq!(creators.len(), 1);

let creator = creators.pop().unwrap();
assert_eq!(creator.value, "Llywelyn");
// assert_eq!(creator.options, 0);
// TO DO: Implement this test when options are exposed.
}

#[test]
fn happy_path_creator_bag() {
let m = XmpMeta::from_str(PURPLE_SQUARE_XMP).unwrap();

let mut subjects: Vec<String> = m
.array_property("http://purl.org/dc/elements/1.1/", "subject")
.map(|v| v.value)
.collect();

subjects.sort();

assert_eq!(
subjects,
vec!("Stefan", "XMP", "XMPFiles", "purple", "square", "test")
);
}

#[test]
fn no_such_property() {
let m = XmpMeta::from_str(PURPLE_SQUARE_XMP).unwrap();

let first_creator = m
.array_property("http://purl.org/dc/elements/1.1/", "creatorx")
.next();

assert!(first_creator.is_none());
}
}
80 changes: 79 additions & 1 deletion src/xmp_meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ impl XmpMeta {
}
}

/// Gets a property value.
/// Gets a simple string 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
Expand Down Expand Up @@ -218,6 +218,24 @@ impl XmpMeta {
XmpError::raise_from_c(&err)
}

/// Creates an iterator for an array property value.
///
/// ## Arguments
///
/// * `schema_ns`: The namespace URI; see [`XmpMeta::property()`].
///
/// * `prop_name`: The name of the property. Can be a general path
/// expression. Must not be an empty string. See [`XmpMeta::property()`]
/// for namespace prefix usage.
pub fn array_property(&self, schema_ns: &str, prop_name: &str) -> ArrayProperty {
ArrayProperty {
meta: self,
ns: CString::new(schema_ns).unwrap_or_default(),
name: CString::new(prop_name).unwrap_or_default(),
index: 1,
}
}

/// Reports whether a property currently exists.
///
/// ## Arguments
Expand Down Expand Up @@ -261,3 +279,63 @@ impl FromStr for XmpMeta {
Ok(XmpMeta { m })
}
}

/// An XMP value consists describes a simple property or an item in an
/// array property.
#[non_exhaustive]
pub struct XmpValue {
/// String value for this item.
pub value: String,

/// Flags that further describe this item. (NOT YET IMPLEMENTED)
pub options: XmpOptions,
}

/// Flags that provide additional description for an [`XmpValue`].
///
/// Not currently implemented.
pub struct XmpOptions {
#[allow(dead_code)] // TEMPORARY until we provide accessors for this
options: u32,
}

/// An iterator that provides access to items within a property array.
///
/// Create via [`XmpMeta::array_property`].
pub struct ArrayProperty<'a> {
meta: &'a XmpMeta,
ns: CString,
name: CString,
index: u32,
}

impl<'a> Iterator for ArrayProperty<'a> {
type Item = XmpValue;

fn next(&mut self) -> Option<Self::Item> {
unsafe {
let mut options: u32 = 0;
let mut err = ffi::CXmpError::default();

let c_result = ffi::CXmpMetaGetArrayItem(
self.meta.m,
&mut err,
self.ns.as_ptr(),
self.name.as_ptr(),
self.index,
&mut options,
);

self.index += 1;

if c_result.is_null() {
None
} else {
Some(XmpValue {
value: CStr::from_ptr(c_result).to_string_lossy().into_owned(),
options: XmpOptions { options },
})
}
}
}
}