Skip to content
Open
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
30 changes: 29 additions & 1 deletion src/core/metadata_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -128,6 +150,12 @@ class MetadataParser {
case "dc:subject":
this._parseArray(entry);
continue;
case "dc:title":
case "dc:description":

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the XMP specs, dc:rights and xmpRights:UsageTerms has a language alternative too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added both.

case "dc:rights":
case "xmprights:usageterms":
this._parseLangAlt(entry);
continue;
}
this._metadataMap.set(name, entry.textContent.trim());
}
Expand Down
58 changes: 58 additions & 0 deletions test/unit/metadata_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this test and the others, make sure we've a way to know what's failing exactly (in case something is failing) in using withContext.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, each assertion now has a context with the property name and the input.

for (const name of [
"dc:title",
"dc:description",
"dc:rights",
"xmpRights:UsageTerms",
]) {
for (const alternatives of [
'<rdf:li xml:lang="x-default">Hello World</rdf:li>' +
'<rdf:li xml:lang="en">Hello World</rdf:li>',
'<rdf:li xml:lang="fr">Bonjour le monde</rdf:li>' +
'<rdf:li xml:lang="x-default">Hello World</rdf:li>',
'<rdf:li xml:lang="fr">Bonjour le monde</rdf:li>' +
'<rdf:li xml:lang="X-Default">Hello World</rdf:li>',
]) {
const data =
"<rdf:RDF><rdf:Description>" +
`<${name}><rdf:Alt>${alternatives}</rdf:Alt></${name}>` +
"</rdf:Description></rdf:RDF>";

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 =
"<rdf:RDF><rdf:Description>" +
`<${name}><rdf:Alt>` +
'<rdf:li xml:lang="en">Hello World</rdf:li>' +
'<rdf:li xml:lang="fr">Bonjour le monde</rdf:li>' +
`</rdf:Alt></${name}>` +
"</rdf:Description></rdf:RDF>";

expect(createMetadata(data).get(name.toLowerCase()))
.withContext(name)
.toEqual("Hello World");
}
});

it("should handle whitespace before the language alternatives", function () {
const data =
"<rdf:RDF><rdf:Description><dc:title>\n <rdf:Alt>\n" +
' <rdf:li xml:lang="en">English title</rdf:li>\n' +
' <rdf:li xml:lang="x-default"> Default title </rdf:li>\n' +
"</rdf:Alt>\n</dc:title></rdf:Description></rdf:RDF>";

expect(createMetadata(data).get("dc:title")).toEqual("Default title");
});

it("should repair and handle invalid metadata", function () {
const data =
"<x:xmpmeta xmlns:x='adobe:ns:meta/'>" +
Expand Down
Loading