diff --git a/src/core/metadata_parser.js b/src/core/metadata_parser.js
index f50927e43db57..a4cbfa12d59c0 100644
--- a/src/core/metadata_parser.js
+++ b/src/core/metadata_parser.js
@@ -21,7 +21,10 @@ class MetadataParser {
data = this._repair(data);
// Convert the string to an XML document.
- const parser = new SimpleXMLParser({ lowerCaseName: true });
+ const parser = new SimpleXMLParser({
+ lowerCaseName: true,
+ hasAttributes: true,
+ });
const xmlDocument = parser.parseFromString(data);
this._metadataMap = new Map();
@@ -99,6 +102,25 @@ class MetadataParser {
);
}
+ _parseLangAlt(entry) {
+ const alt = entry.children.find(node => node.nodeName === "rdf:alt");
+ const alternatives = alt ? this._getSequence(alt) : [];
+ // Language alternatives represent the same value in different languages.
+ // Prefer the default value, without changing the string-valued API.
+ const selected =
+ alternatives.find(node =>
+ node.attributes.some(
+ ({ name, value }) =>
+ name === "xml:lang" && value.toLowerCase() === "x-default"
+ )
+ ) || alternatives[0];
+
+ this._metadataMap.set(
+ entry.nodeName,
+ (selected || entry).textContent.trim()
+ );
+ }
+
_parse(xmlDocument) {
let rdf = xmlDocument.documentElement;
@@ -128,6 +150,12 @@ class MetadataParser {
case "dc:subject":
this._parseArray(entry);
continue;
+ case "dc:title":
+ case "dc:description":
+ case "dc:rights":
+ case "xmprights:usageterms":
+ this._parseLangAlt(entry);
+ continue;
}
this._metadataMap.set(name, entry.textContent.trim());
}
diff --git a/test/unit/metadata_spec.js b/test/unit/metadata_spec.js
index 4bb5afde7cb03..618113907361e 100644
--- a/test/unit/metadata_spec.js
+++ b/test/unit/metadata_spec.js
@@ -37,6 +37,64 @@ describe("metadata", function () {
expect([...metadata]).toEqual([["dc:title", "Foo bar baz"]]);
});
+ it("should select the default language alternative (issue 20801)", function () {
+ for (const name of [
+ "dc:title",
+ "dc:description",
+ "dc:rights",
+ "xmpRights:UsageTerms",
+ ]) {
+ for (const alternatives of [
+ 'Hello World' +
+ 'Hello World',
+ 'Bonjour le monde' +
+ 'Hello World',
+ 'Bonjour le monde' +
+ 'Hello World',
+ ]) {
+ const data =
+ "" +
+ `<${name}>${alternatives}${name}>` +
+ "";
+
+ expect(createMetadata(data).get(name.toLowerCase()))
+ .withContext(`${name}: ${alternatives}`)
+ .toEqual("Hello World");
+ }
+ }
+ });
+
+ it("should use the first language alternative if there is no default", function () {
+ for (const name of [
+ "dc:title",
+ "dc:description",
+ "dc:rights",
+ "xmpRights:UsageTerms",
+ ]) {
+ const data =
+ "" +
+ `<${name}>` +
+ 'Hello World' +
+ 'Bonjour le monde' +
+ `${name}>` +
+ "";
+
+ expect(createMetadata(data).get(name.toLowerCase()))
+ .withContext(name)
+ .toEqual("Hello World");
+ }
+ });
+
+ it("should handle whitespace before the language alternatives", function () {
+ const data =
+ "\n \n" +
+ ' English title\n' +
+ ' Default title \n' +
+ "\n";
+
+ expect(createMetadata(data).get("dc:title")).toEqual("Default title");
+ });
+
it("should repair and handle invalid metadata", function () {
const data =
"" +