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 XmpMeta::set_localized_text #133

Merged
merged 2 commits into from
Nov 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/ffi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
11 changes: 11 additions & 0 deletions src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
13 changes: 13 additions & 0 deletions src/tests/fixtures/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,16 @@ pub(crate) const QUAL_EXAMPLE: &str = r#"
</rdf:Description>
</rdf:RDF>
"#;

pub(crate) const LOCALIZED_TEXT_EXAMPLE: &str = r#"<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<rdf:Description rdf:about="">
<dc:title>
<rdf:Alt>
<rdf:li xml:lang="x-default">XMP - Extensible Metadata Platform</rdf:li>
<rdf:li xml:lang="en-us">XMP - Extensible Metadata Platform (US English)</rdf:li>
<rdf:li xml:lang="fr">XMP - Une Platforme Extensible pour les Métadonnées</rdf:li>
</rdf:Alt>
</dc:title>
</rdf:Description>
</rdf:RDF>"#;
119 changes: 99 additions & 20 deletions src/tests/xmp_meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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#"<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<rdf:Description rdf:about="">
<dc:title>
<rdf:Alt>
<rdf:li xml:lang="x-default">
XMP - Extensible Metadata Platform
</rdf:li>
<rdf:li xml:lang="en-us">
XMP - Extensible Metadata Platform (US English)
</rdf:li>
<rdf:li xml:lang="fr">
XMP - Une Platforme Extensible pour les Métadonnées
</rdf:li>
</rdf:Alt>
</dc:title>
</rdf:Description>
</rdf:RDF>"#;
use crate::{tests::fixtures::LOCALIZED_TEXT_EXAMPLE, xmp_ns, xmp_value::xmp_prop, XmpMeta};

#[test]
fn happy_path() {
Expand Down Expand Up @@ -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(), "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"XMP Core 6.0.0\"> <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"> <rdf:Description rdf:about=\"\" xmlns:ns1=\"ns:test1/\"> <ns1:AltText> <rdf:Alt> <rdf:li xml:lang=\"x-default\">en-us value</rdf:li> <rdf:li xml:lang=\"en-US\">en-us value</rdf:li> <rdf:li xml:lang=\"en-UK\">en-uk value</rdf:li> </rdf:Alt> </ns1:AltText> </rdf:Description> </rdf:RDF> </x:xmpmeta>");
}

#[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};

Expand Down
89 changes: 86 additions & 3 deletions src/xmp_meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down