From 7df67b91a673f3f53b4afa2000f25d3798b1c565 Mon Sep 17 00:00:00 2001 From: Eric Scouten Date: Tue, 25 Oct 2022 21:30:44 -0700 Subject: [PATCH] Add `XmpMeta::set_struct_field` (#118) --- src/ffi.cpp | 23 +++++++- src/ffi.rs | 11 ++++ src/tests/xmp_meta.rs | 132 ++++++++++++++++++++++++++++++++++++++++++ src/xmp_meta.rs | 53 +++++++++++++++++ 4 files changed, 218 insertions(+), 1 deletion(-) diff --git a/src/ffi.cpp b/src/ffi.cpp index 18d2fe6..b65fd8d 100644 --- a/src/ffi.cpp +++ b/src/ffi.cpp @@ -699,7 +699,6 @@ extern "C" { #endif } - const char* CXmpMetaGetStructField(CXmpMeta* m, CXmpError* outError, const char* schemaNS, @@ -728,6 +727,28 @@ extern "C" { return NULL; } + void CXmpMetaSetStructField(CXmpMeta* m, + CXmpError* outError, + const char* schemaNS, + const char* structName, + const char* fieldNS, + const char* fieldName, + const char* itemValue, + AdobeXMPCommon::uint32 itemOptions) { + #ifndef NOOP_FFI + try { + m->m.SetStructField(schemaNS, structName, fieldNS, fieldName, + itemValue, itemOptions); + } + catch (XMP_Error& e) { + copyErrorForResult(e, outError); + } + catch (...) { + signalUnknownError(outError); + } + #endif + } + int CXmpMetaDoesPropertyExist(CXmpMeta* m, const char* schemaNS, const char* propName) { diff --git a/src/ffi.rs b/src/ffi.rs index 70c0d3b..fbf53c5 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -316,6 +316,17 @@ extern "C" { item_options: u32, ); + pub(crate) fn CXmpMetaSetStructField( + meta: *mut CXmpMeta, + out_error: *mut CXmpError, + schema_ns: *const c_char, + struct_name: *const c_char, + field_ns: *const c_char, + field_name: *const c_char, + item_value: *const c_char, + item_options: u32, + ); + pub(crate) fn CXmpMetaDoesPropertyExist( meta: *const CXmpMeta, schema_ns: *const c_char, diff --git a/src/tests/xmp_meta.rs b/src/tests/xmp_meta.rs index c684a62..efd0337 100644 --- a/src/tests/xmp_meta.rs +++ b/src/tests/xmp_meta.rs @@ -1497,6 +1497,138 @@ mod append_array_item { } } +mod set_struct_field { + use std::str::FromStr; + + use crate::{tests::fixtures, xmp_ns, xmp_value::xmp_prop, XmpErrorType, XmpMeta, XmpValue}; + + #[test] + fn happy_path() { + let mut m = XmpMeta::from_str(fixtures::STRUCT_EXAMPLE).unwrap(); + + assert_eq!( + m.struct_field( + xmp_ns::IPTC_CORE, + "CreatorContactInfo", + xmp_ns::IPTC_CORE, + "CiAdrPcode" + ) + .unwrap(), + XmpValue { + value: "98110".to_owned(), + options: 0 + } + ); + + m.set_struct_field( + xmp_ns::IPTC_CORE, + "CreatorContactInfo", + xmp_ns::IPTC_CORE, + "CiAdrPcode", + &XmpValue::from("95110"), + ) + .unwrap(); + + assert_eq!( + m.struct_field( + xmp_ns::IPTC_CORE, + "CreatorContactInfo", + xmp_ns::IPTC_CORE, + "CiAdrPcode" + ) + .unwrap(), + XmpValue { + value: "95110".to_owned(), + options: 0 + } + ); + } + + #[test] + fn item_options() { + let mut m = XmpMeta::from_str(fixtures::STRUCT_EXAMPLE).unwrap(); + + m.set_struct_field( + xmp_ns::IPTC_CORE, + "CreatorContactInfo", + xmp_ns::IPTC_CORE, + "CiAdrPcode", + &XmpValue::from("95110").set_is_uri(true), + ) + .unwrap(); + + assert_eq!( + m.struct_field( + xmp_ns::IPTC_CORE, + "CreatorContactInfo", + xmp_ns::IPTC_CORE, + "CiAdrPcode" + ) + .unwrap(), + XmpValue { + value: "95110".to_owned(), + options: xmp_prop::VALUE_IS_URI + } + ); + } + + #[test] + fn init_fail() { + let mut m = XmpMeta::new_fail(); + + let err = m + .set_struct_field( + xmp_ns::IPTC_CORE, + "CreatorContactInfo", + xmp_ns::IPTC_CORE, + "CiAdrPcode", + &XmpValue::from("95110"), + ) + .unwrap_err(); + + assert_eq!(err.error_type, XmpErrorType::NoCppToolkit); + } + + #[test] + fn error_empty_struct_name() { + let mut m = XmpMeta::default(); + + let err = m + .set_struct_field( + xmp_ns::IPTC_CORE, + "", + xmp_ns::IPTC_CORE, + "CiAdrPcode", + &XmpValue::from("95110"), + ) + .unwrap_err(); + + assert_eq!(err.error_type, XmpErrorType::BadXPath); + assert_eq!(err.debug_message, "Empty struct name"); + } + + #[test] + fn error_nul_in_name() { + let mut m = XmpMeta::default(); + + let err = m + .set_struct_field( + xmp_ns::IPTC_CORE, + "x\0x", + xmp_ns::IPTC_CORE, + "CiAdrPcode", + &XmpValue::from("95110"), + ) + .unwrap_err(); + + assert_eq!(err.error_type, XmpErrorType::NulInRustString); + assert_eq!( + err.debug_message, + "Unable to convert to C string because a NUL byte was found" + ); + } +} + mod localized_text { use std::str::FromStr; diff --git a/src/xmp_meta.rs b/src/xmp_meta.rs index c66db7e..02fa939 100644 --- a/src/xmp_meta.rs +++ b/src/xmp_meta.rs @@ -832,6 +832,59 @@ impl XmpMeta { } } + /// Creates or sets the value of a field within a nested structure, + /// using a string value. + /// + /// Use this function to set a value within an existing structure, + /// create a new field within an existing structure, or create an + /// empty structure of any depth. If you set a field in a structure + /// that does not exist, the structure is automatically created. + /// + /// Use [`XmpMeta::compose_struct_field_path()`] to create a complex path. + /// + /// ## Arguments + /// + /// * `namespace` and `struct_name`: See [Accessing + /// properties](#accessing-properties). + /// * `field_ns` and `field_name` take the same form (i.e. see [Accessing + /// properties](#accessing-properties) again.) + /// * `item_value`: Contains value and flags for the item to be added to the + /// array. + pub fn set_struct_field( + &mut self, + namespace: &str, + struct_name: &str, + field_ns: &str, + field_name: &str, + item_value: &XmpValue, + ) -> XmpResult<()> { + if let Some(m) = self.m { + let c_struct_ns = CString::new(namespace)?; + let c_struct_name = CString::new(struct_name.as_bytes())?; + let c_field_ns = CString::new(field_ns)?; + let c_field_name = CString::new(field_name.as_bytes())?; + let c_item_value = CString::new(item_value.value.as_bytes())?; + let mut err = ffi::CXmpError::default(); + + unsafe { + ffi::CXmpMetaSetStructField( + m, + &mut err, + c_struct_ns.as_ptr(), + c_struct_name.as_ptr(), + c_field_ns.as_ptr(), + c_field_name.as_ptr(), + c_item_value.as_ptr(), + item_value.options, + ); + } + + XmpError::raise_from_c(&err) + } else { + Err(no_cpp_toolkit()) + } + } + /// Retrieves information about a selected item from an alt-text array. /// /// Localized text properties are stored in alt-text arrays. They allow