diff --git a/crates/preflight/src/tests.rs b/crates/preflight/src/tests.rs
index 8eea6afeb1..23677867a2 100644
--- a/crates/preflight/src/tests.rs
+++ b/crates/preflight/src/tests.rs
@@ -708,3 +708,21 @@ fn xmp_value_types_are_reported_by_verify() {
);
assert_eq!(issues(&doc).len(), 1);
}
+
+/// PDF/A-2 6.6.2.3.1, the RDF/XML value forms XMP allows (ISO 16684-1 7.9.2.5): one `rdf:value`
+/// in a qualified value, no parse type but `Resource`, and an `rdf:resource` element left empty.
+#[test]
+fn xmp_value_forms_are_those_xmp_allows() {
+ // A qualified value has exactly one rdf:value.
+ assert_eq!(
+ mistyped(r#">5five"#),
+ ["crs:Contrast"]
+ );
+ // Only parseType="Resource" has a place in XMP; an rdf:resource element holds no element.
+ assert_eq!(mistyped(r#">5"#), ["crs:Contrast"]);
+ assert_eq!(mistyped(r#">"#), ["xmpMM:DerivedFrom"]);
+ assert_eq!(
+ mistyped(r#">"#),
+ ["xmpMM:DerivedFrom"]
+ );
+}
diff --git a/crates/preflight/src/xmp_types.rs b/crates/preflight/src/xmp_types.rs
index 7ef694ba2f..84f3ce6d9b 100644
--- a/crates/preflight/src/xmp_types.rs
+++ b/crates/preflight/src/xmp_types.rs
@@ -535,6 +535,11 @@ fn fits(ty: Ty, e: &Node<'_, '_>, depth: usize) -> bool {
let children: Vec> = e.children().filter(Node::is_element).collect();
// A qualified value: `rdf:value` holds the value itself (ISO 16684-1 7.9.2.5).
let resource = e.attribute((RDF, "parseType")) == Some("Resource");
+ // `parseType="Resource"` is the only parse type XMP has (ISO 16684-1 7.9.2.5); an
+ // `rdf:resource` element is empty.
+ if e.attribute((RDF, "parseType")).is_some_and(|p| p != "Resource") || e.attribute((RDF, "resource")).is_some() && !children.is_empty() {
+ return false;
+ }
let description = match children.as_slice() {
[d] if is(d, RDF, "Description") => Some(*d),
_ => None,
@@ -543,10 +548,13 @@ fn fits(ty: Ty, e: &Node<'_, '_>, depth: usize) -> bool {
// attribute of the description or of an empty property element. Its qualifiers that are
// predefined properties keep their types too.
let node = if resource { Some(*e) } else { description };
- if let Some(n) = node
- && let Some(v) = n.children().find(|c| is(c, RDF, "value"))
- {
- return blank(e) && blank(&n) && fits(ty, &v, deeper) && predefined_ok(&n, deeper);
+ if let Some(n) = node {
+ // A qualified value has one `rdf:value`.
+ match n.children().filter(|c| is(c, RDF, "value")).collect::>().as_slice() {
+ [] => {}
+ [v] => return blank(e) && blank(&n) && fits(ty, v, deeper) && predefined_ok(&n, deeper),
+ _ => return false,
+ }
}
let attribute_node = description.or_else(|| (children.is_empty() && !resource).then_some(*e));
if let Some(n) = attribute_node