diff --git a/src/ffi.cpp b/src/ffi.cpp
index a4543d4..65c92d1 100644
--- a/src/ffi.cpp
+++ b/src/ffi.cpp
@@ -1056,6 +1056,32 @@ extern "C" {
return NULL;
}
+ void CXmpMetaSetLocalizedText(CXmpMeta* m,
+ CXmpError* outError,
+ const char* schemaNS,
+ const char* altTextName,
+ const char* genericLang,
+ const char* specificLang,
+ const char* itemValue, const char** actualLang,
+ AdobeXMPCommon::uint32 options) {
+ #ifndef NOOP_FFI
+ try {
+ m->m.SetLocalizedText(schemaNS,
+ altTextName,
+ genericLang,
+ specificLang,
+ itemValue,
+ options);
+ }
+ catch (XMP_Error& e) {
+ copyErrorForResult(e, outError);
+ }
+ catch (...) {
+ signalUnknownError(outError);
+ }
+ #endif
+ }
+
const char* CXmpMetaGetObjectName(CXmpMeta* m, CXmpError* outError) {
#ifndef NOOP_FFI
try {
diff --git a/src/ffi.rs b/src/ffi.rs
index a35f308..e09d28a 100644
--- a/src/ffi.rs
+++ b/src/ffi.rs
@@ -451,6 +451,17 @@ extern "C" {
out_options: *mut u32,
) -> *const c_char;
+ pub(crate) fn CXmpMetaSetLocalizedText(
+ meta: *const CXmpMeta,
+ out_error: *mut CXmpError,
+ schema_ns: *const c_char,
+ alt_text_name: *const c_char,
+ generic_lang: *const c_char,
+ specific_lang: *const c_char,
+ item_value: *const c_char,
+ options: u32,
+ );
+
pub(crate) fn CXmpMetaGetObjectName(
meta: *mut CXmpMeta,
out_error: *mut CXmpError,
diff --git a/src/tests/fixtures/mod.rs b/src/tests/fixtures/mod.rs
index 7b1bf02..676340a 100644
--- a/src/tests/fixtures/mod.rs
+++ b/src/tests/fixtures/mod.rs
@@ -170,3 +170,16 @@ pub(crate) const QUAL_EXAMPLE: &str = r#"
"#;
+
+pub(crate) const LOCALIZED_TEXT_EXAMPLE: &str = r#"
+
+
+
+ XMP - Extensible Metadata Platform
+ XMP - Extensible Metadata Platform (US English)
+ XMP - Une Platforme Extensible pour les Métadonnées
+
+
+
+ "#;
diff --git a/src/tests/xmp_meta.rs b/src/tests/xmp_meta.rs
index 412ed4d..cad42b0 100644
--- a/src/tests/xmp_meta.rs
+++ b/src/tests/xmp_meta.rs
@@ -2709,26 +2709,7 @@ mod delete_qualifier {
mod localized_text {
use std::str::FromStr;
- use crate::{xmp_ns, xmp_value::xmp_prop, XmpMeta};
-
- const LOCALIZED_TEXT_EXAMPLE: &str = r#"
-
-
-
-
- XMP - Extensible Metadata Platform
-
-
- XMP - Extensible Metadata Platform (US English)
-
-
- XMP - Une Platforme Extensible pour les Métadonnées
-
-
-
-
- "#;
+ use crate::{tests::fixtures::LOCALIZED_TEXT_EXAMPLE, xmp_ns, xmp_value::xmp_prop, XmpMeta};
#[test]
fn happy_path() {
@@ -2837,6 +2818,104 @@ mod localized_text {
}
}
+mod set_localized_text {
+ use std::str::FromStr;
+
+ use crate::{
+ tests::fixtures::LOCALIZED_TEXT_EXAMPLE, xmp_ns, xmp_value::xmp_prop, XmpError,
+ XmpErrorType, XmpMeta, XmpValue,
+ };
+
+ #[test]
+ fn happy_path() {
+ let mut m = XmpMeta::from_str(LOCALIZED_TEXT_EXAMPLE).unwrap();
+
+ assert_eq!(
+ m.localized_text(xmp_ns::DC, "title", None, "en-us")
+ .unwrap(),
+ (
+ XmpValue {
+ value: "XMP - Extensible Metadata Platform (US English)".to_owned(),
+ options: xmp_prop::HAS_LANG | xmp_prop::HAS_QUALIFIERS
+ },
+ "en-US".to_owned()
+ )
+ );
+
+ m.set_localized_text(xmp_ns::DC, "title", None, "en-us", "XMP in Rust")
+ .unwrap();
+
+ assert_eq!(
+ m.localized_text(xmp_ns::DC, "title", None, "en-us")
+ .unwrap(),
+ (
+ XmpValue {
+ value: "XMP in Rust".to_owned(),
+ options: xmp_prop::HAS_LANG | xmp_prop::HAS_QUALIFIERS
+ },
+ "en-US".to_owned()
+ )
+ );
+ }
+
+ #[test]
+ fn generic_lang() {
+ let mut m = XmpMeta::default();
+
+ const NS1: &str = "ns:test1/";
+
+ m.set_localized_text(NS1, "AltText", None, "x-default", "default value")
+ .unwrap();
+
+ m.set_localized_text(NS1, "AltText", Some("en"), "en-us", "en-us value")
+ .unwrap();
+
+ m.set_localized_text(NS1, "AltText", Some("en"), "en-uk", "en-uk value")
+ .unwrap();
+
+ assert_eq!(m.to_string(), " en-us value en-us value en-uk value ");
+ }
+
+ #[test]
+ fn init_fail() {
+ let mut m = XmpMeta::new_fail();
+
+ assert_eq!(
+ m.set_localized_text(xmp_ns::DC, "title", None, "en-us", "XMP in Rust"),
+ Err(XmpError {
+ error_type: XmpErrorType::NoCppToolkit,
+ debug_message: "C++ XMP Toolkit not available".to_owned()
+ })
+ );
+ }
+
+ #[test]
+ fn error_empty_struct_name() {
+ let mut m = XmpMeta::default();
+
+ assert_eq!(
+ m.set_localized_text(xmp_ns::XMP, "", None, "CiAdrPcode", "95110",),
+ Err(XmpError {
+ error_type: XmpErrorType::BadXPath,
+ debug_message: "Empty array name".to_owned()
+ })
+ );
+ }
+
+ #[test]
+ fn error_nul_in_name() {
+ let mut m = XmpMeta::default();
+
+ assert_eq!(
+ m.set_localized_text(xmp_ns::XMP, "x\0x", None, "en-US", "95110",),
+ Err(XmpError {
+ error_type: XmpErrorType::BadXPath,
+ debug_message: "Empty array name".to_owned()
+ })
+ );
+ }
+}
+
mod name {
use crate::{XmpErrorType, XmpMeta};
diff --git a/src/xmp_meta.rs b/src/xmp_meta.rs
index 8f2327c..34c25a7 100644
--- a/src/xmp_meta.rs
+++ b/src/xmp_meta.rs
@@ -1349,9 +1349,9 @@ impl XmpMeta {
/// artificial language, `x-default`, that is used to explicitly denote a
/// default item in an alt-text array. The XMP toolkit normalizes alt-text
/// arrays such that the x-default item is the first item. The
- /// `set_localized_text` function has several special features related to
- /// the `x-default` item. See its description for details. The array item
- /// is selected according to these rules:
+ /// [`XmpMeta::set_localized_text()`] function has several special
+ /// features related to the `x-default` item. See its description for
+ /// details. The array item is selected according to these rules:
/// * Look for an exact match with the specific language.
/// * If a generic language is given, look for a partial match.
/// * Look for an `x-default` item.
@@ -1429,6 +1429,89 @@ impl XmpMeta {
}
}
+ /// Modifies the value of a selected item in an alt-text array using a
+ /// string object.
+ ///
+ /// Creates an appropriate array item if necessary, and handles special
+ /// cases for the `x-default` item.
+ ///
+ /// The array item is selected according to these rules:
+ ///
+ /// * Look for an exact match with the specific language.
+ /// * If a generic language is given, look for a partial match.
+ /// * Look for an `x-default` item.
+ /// * Choose the first item.
+ ///
+ /// A partial match with the generic language is where the start of the
+ /// item's language matches the generic string and the next character is
+ /// `-`. An exact match is also recognized as a degenerate case.
+ ///
+ /// You can pass `x-default` as the specific language. In this case,
+ /// selection of an `x-default` item is an exact match by the first rule,
+ /// not a selection by the 3rd rule. The last 2 rules are fallbacks used
+ /// when the specific and generic languages fail to produce a match.
+ ///
+ /// Item values are modified according to these rules:
+ ///
+ /// * If the selected item is from a match with the specific language, the
+ /// value of that item is modified. If the existing value of that item
+ /// matches the existing value of the `x-default` item, the `x-default`
+ /// item is also modified. If the array only has 1 existing item (which is
+ /// not `x-default`), an `x-default` item is added with the given value.
+ /// * If the selected item is from a match with the generic language and
+ /// there are no other generic matches, the value of that item is
+ /// modified. If the existing value of that item matches the existing
+ /// value of the `x-default` item, the `x-default` item is also modified.
+ /// If the array only has 1 existing item (which is not `x-default`), an
+ /// `x-default` item is added with the given value.
+ /// * If the selected item is from a partial match with the generic language
+ /// and there are other partial matches, a new item is created for the
+ /// specific language. The `x-default` item is not modified.
+ /// * If the selected item is from the last 2 rules then a new item is
+ /// created for the specific language. If the array only had an
+ /// `x-default` item, the `x-default` item is also modified. If the array
+ /// was empty, items are created for the specific language and
+ /// `x-default`.
+ pub fn set_localized_text(
+ &mut self,
+ namespace: &str,
+ path: &str,
+ generic_lang: Option<&str>,
+ specific_lang: &str,
+ item_value: &str,
+ ) -> XmpResult<()> {
+ if let Some(m) = self.m {
+ let c_ns = CString::new(namespace).unwrap_or_default();
+ let c_name = CString::new(path).unwrap_or_default();
+ let c_generic_lang = generic_lang.map(|s| CString::new(s).unwrap_or_default());
+ let c_specific_lang = CString::new(specific_lang).unwrap_or_default();
+ let c_item_value = CString::new(item_value).unwrap_or_default();
+
+ let mut err = ffi::CXmpError::default();
+
+ unsafe {
+ ffi::CXmpMetaSetLocalizedText(
+ m,
+ &mut err,
+ c_ns.as_ptr(),
+ c_name.as_ptr(),
+ match c_generic_lang {
+ Some(lang) => lang.as_ptr(),
+ None => std::ptr::null(),
+ },
+ c_specific_lang.as_ptr(),
+ c_item_value.as_ptr(),
+ 0,
+ );
+ };
+
+ XmpError::raise_from_c(&err)?;
+ Ok(())
+ } else {
+ Err(no_cpp_toolkit())
+ }
+ }
+
/// Composes the path expression for an item in an array.
///
/// ## Arguments