Skip to content

Commit 32aaca8

Browse files
committed
Support XMP lang-alt and xmpRights in portable XMP
Decode and emit XMP language-alt (rdf:Alt) items and add xmpRights namespace support. xmp_decode: validate and append xml:lang attributes to property paths (e.g. title[@xml:lang=x-default]) and set frame flags for Alt containers. xmp_dump: recognize xmpRights namespace, parse/validate lang-alt property names, track generated shapes and lang-alt keys, claim existing lang-alt entries, and emit rdf:Alt groups with rdf:li xml:lang entries. Adds data structures and logic to prefer/replace x-default alt text when canonicalizing managed namespaces, respects limits and safe-lang validation, and integrates lang-alt handling into the portable property collection/emission pipeline. Tests updated/added to cover decoding, dumping, and transfer behaviors for lang-alt, xmpRights, and canonicalization cases.
1 parent ebbe735 commit 32aaca8

6 files changed

Lines changed: 978 additions & 65 deletions

File tree

src/include/openmeta/xmp_dump.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,9 @@ struct XmpPortableOptions final {
8383
bool include_iptc = true;
8484
/// Include \ref MetaKeyKind::XmpProperty entries already present in the store.
8585
///
86-
/// \note Currently only simple `property_path` values are emitted (no `/` nesting).
86+
/// \note Currently simple `property_path` values, indexed `[n]` paths,
87+
/// and bounded lang-alt paths like `title[@xml:lang=x-default]` are
88+
/// emitted (no `/` nesting).
8789
bool include_existing_xmp = false;
8890
/// Existing XMP namespace writeback policy for portable output.
8991
XmpExistingNamespacePolicy existing_namespace_policy

src/openmeta/xmp_decode.cc

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ namespace {
171171
bool is_rdf = false;
172172
bool is_description = false;
173173
bool is_array_container = false;
174+
bool is_alt_container = false;
174175
bool is_li = false;
175176
bool is_nonrdf = false;
176177
bool contributed_to_path = false;
@@ -306,6 +307,58 @@ namespace {
306307
return true;
307308
}
308309

310+
static bool path_append_lang_alt(Ctx* ctx, std::string_view lang) noexcept
311+
{
312+
if (!ctx || lang.empty()) {
313+
return false;
314+
}
315+
316+
for (size_t i = 0; i < lang.size(); ++i) {
317+
const char c = lang[i];
318+
const bool ok = (c >= 'A' && c <= 'Z')
319+
|| (c >= 'a' && c <= 'z')
320+
|| (c >= '0' && c <= '9') || c == '-';
321+
if (!ok) {
322+
return false;
323+
}
324+
}
325+
326+
static constexpr std::string_view kPrefix = "[@xml:lang=";
327+
const uint32_t max_path = ctx->options.limits.max_path_bytes;
328+
uint64_t needed = static_cast<uint64_t>(ctx->path.size())
329+
+ static_cast<uint64_t>(kPrefix.size())
330+
+ static_cast<uint64_t>(lang.size()) + 1U;
331+
if (max_path != 0U && needed > max_path) {
332+
stop_parser(ctx, XmpDecodeStatus::LimitExceeded);
333+
return false;
334+
}
335+
336+
ctx->path.append(kPrefix.data(), kPrefix.size());
337+
ctx->path.append(lang.data(), lang.size());
338+
ctx->path.push_back(']');
339+
return true;
340+
}
341+
342+
static bool find_xml_lang_attr(const XML_Char** atts,
343+
std::string_view* out_lang) noexcept
344+
{
345+
if (!atts || !out_lang) {
346+
return false;
347+
}
348+
*out_lang = {};
349+
for (int i = 0; atts[i] && atts[i + 1]; i += 2) {
350+
const std::string_view an(atts[i], std::strlen(atts[i]));
351+
const NameParts ap = split_name(an);
352+
if (ap.uri != kXmlNs || ap.local != "lang") {
353+
continue;
354+
}
355+
*out_lang = trim_ascii_ws(
356+
std::string_view(atts[i + 1], std::strlen(atts[i + 1])));
357+
return !out_lang->empty();
358+
}
359+
return false;
360+
}
361+
309362

310363
static Frame* find_nearest_array_container(Ctx* ctx) noexcept
311364
{
@@ -396,6 +449,7 @@ namespace {
396449
frame.is_rdf = is_rdf;
397450
frame.is_description = is_desc;
398451
frame.is_array_container = is_seq;
452+
frame.is_alt_container = is_rdf && (parts.local == "Alt");
399453
frame.is_li = is_li;
400454
frame.is_nonrdf = (!is_rdf && !is_xml);
401455
frame.path_len_before = static_cast<uint32_t>(ctx->path.size());
@@ -463,7 +517,15 @@ namespace {
463517
container->li_counter += 1;
464518
frame.path_len_before = static_cast<uint32_t>(ctx->path.size());
465519
frame.contributed_to_path = true;
466-
if (!path_append_index(ctx, container->li_counter)) {
520+
if (container->is_alt_container) {
521+
std::string_view lang;
522+
if (find_xml_lang_attr(atts, &lang)
523+
&& path_append_lang_alt(ctx, lang)) {
524+
// done
525+
} else if (!path_append_index(ctx, container->li_counter)) {
526+
return;
527+
}
528+
} else if (!path_append_index(ctx, container->li_counter)) {
467529
return;
468530
}
469531
}

0 commit comments

Comments
 (0)