"use strict";
define(["jquery", "underscore", "backbone", "collections/Citations"], function (
$,
_,
Backbone,
Citations,
) {
/**
* @class CitationModel
* @classdesc A Citation Model represents a single Citation Object returned by
* the metrics-service. A citation model can alternatively be populated with a
* SolrResultsModel or a DataONEObjectModel, or an extension of either of
* those models. A Citation Model can represent a citation to a local
* MetacatUI object, or an external document or publication.
* @classcategory Models
* @extends Backbone.Model
* @see https://app.swaggerhub.com/apis/nenuji/data-metrics
*/
var Citation = Backbone.Model.extend(
/** @lends CitationModel.prototype */ {
/**
* The name of this type of model
* @type {string}
*/
type: "CitationModel",
/**
* The default Citation fields
* @name CitationModel#defaults
* @type {Object}
* @property {string} origin - text of authors who published the source
* dataset / document / article
* @property {string[]} originArray - array of authors who published the
* source dataset / document / article. Same as origin, but split on commas
* and trimmed.
* @property {string} title - Title of the source dataset / document /
* article
* @property {number} year_of_publishing - Year in which the source dataset
* / document / article was published
* @property {string} source_url - URL to the source dataset / document /
* article. This is usually an external publication that cites one or more
* DataONE datasets.
* @property {string} source_id - Unique identifier for the source dataset /
* document / article that cited the target dataset. This is usually an
* external publication that cites one or more DataONE datasets.
* @property {string} target_id - Unique identifier to the target DATAONE
* dataset. This is the dataset that was cited by the "source" document.
* @property {string} publisher - Publisher for the source dataset /
* document / article
* @property {string} journal - The journal where the the document was
* published
* @property {number|string} volume - The volume of the journal where the
* document was published
* @property {number} page - The page of the journal where the document was
* published
* @property {Citations} citationMetadata - When this Citation Model refers
* to an external document, citationMetadata is a collection of DataONE
* datasets that the external document cites. This info is retrieved by the
* metrics service, then parsed and stored as a collection of Citation
* Models. This attribute is used in the Portals view, for example, where we
* display a list of external publications that cite the portal data. In
* this case, each publication's citationMetadata is the list of local
* MetacatUI data packages cited in the publication.
* @property {Backbone.Model} sourceModel - The model to use to populate
* this citation model. This can be a SolrResultsModel, a
* DataONEObjectModel, or an extension of either of those models. Do not set
* this attribute directly. Instead, use the setSourceModel() method.
* @property {string} pid - The pid or unique identifier of the object being
* cited.
* @property {string} seriesId - The seriesId of the object being cited
* @property {string} view_url - For citations that are in the local
* MetacatUI repository, this is the URL to the metadata view page for the
* object being cited.
* @property {string} pid_url - If the pid is a DOI, then this is the URL to
* the DOI landing page for the object being cited. This will automatically
* be set when the pid attribute is set.
* @property {string} seriesId_url - If the seriesId is a DOI, then this is
* the URL to the DOI landing page for the object being cited. This will
* automatically be set when the seriesId attribute is set.
*/
defaults: function () {
return {
origin: null,
originArray: [],
title: null,
year_of_publishing: null,
source_url: null,
source_id: null,
target_id: null,
publisher: null,
journal: null,
volume: null,
page: null,
citationMetadata: null,
sourceModel: null,
pid: null,
seriesId: null,
view_url: null,
pid_url: null,
seriesId_url: null,
};
},
/**
* Get the attribute getters for this model. "Attribute getters" are
* functions that return the value of an attribute for this Citation Model
* given a source model. The source model can be a SolrResultsModel, a
* DataONEObjectModel, or an extension of either of those models.
* @returns {Object} - An object that maps the name of the CitationModel
* attribute to the function that returns the value for that attribute.
*/
attrGetters: function () {
return {
year_of_publishing: this.getYearFromSourceModel,
title: this.getTitleFromSourceModel,
journal: this.getJournalFromSourceModel,
pid: this.getPidFromSourceModel,
seriesId: this.getSeriesIdFromSourceModel,
originArray: this.getOriginArrayFromSourceModel,
view_url: this.getViewUrlFromSourceModel,
};
},
/**
* Override the default Backbone.Model.parse() method to convert the
* citationMetadata object into a nested collection of CitationModels.
* @param {Object} response - The response from the metrics-service API
* @param {Object} options - Options to pass to the parse() method.
* @returns {Object} The parsed response
*/
parse: function (response) {
try {
// strings that need formatting when coming from the metrics-service:
const toFormat = ["journal", "page", "volume", "publisher"];
toFormat.forEach((attr) => {
response[attr] = this.formatMetricsServiceString(response[attr]);
}, this);
// Turn the author strings into CSL JSON objects
if (response.origin) {
const or = this.originToArray(response.origin);
}
let sID = response.source_id;
if (this.isDOI(sID)) {
if (sID.startsWith("http")) {
sID = this.URLtoDOI(sID);
}
if (!sID.startsWith("doi:")) {
sID = "doi:" + sID;
}
response.source_id = sID;
}
// Format the citation metadata = DataONE datasets cited by this
// citation (external document)
const cm = response.citationMetadata;
if (cm) {
if (cm && !(cm instanceof Citations)) {
const citationMetadata = Object.entries(cm).map(([pid, data]) => {
// Convert format from {id: {data}} to {data, id}
const item = { ...data, pid };
// Origin returned by metrics-service is actually an array, not a
// string
item.originArray = item.origin;
delete item.origin;
// Format the authors in the origin array
item.originArray = item.originArray.map((author) =>
this.formatAuthor(author),
);
// Get the publish year
const date =
item.datePublished || item.dateUpdated || item.dateModified;
item.year_of_publishing = date
? new Date(date).getUTCFullYear()
: null;
// Because the citation metadata is always referencing an object
// in the local MetacatUI repository, we assume that the view_url
// exists for the given PID.
// DOIs from the metrics service are not prefixed with "doi:"
if (this.isDOI(pid) && !pid.startsWith("doi:")) {
pid = "doi:" + pid;
}
item.pid = pid;
item.view_url =
MetacatUI.root + "/view/" + encodeURIComponent(pid);
return item;
});
response.citationMetadata = new Citations(citationMetadata);
}
}
return response;
} catch (error) {
console.log(
"Error parsing a CitationModel. Returning response as-is.",
error,
);
return response;
}
},
/**
* Override the default Backbone.Model.set() method to format the title,
* page, and volume attributes before setting them, and ensure that
* attributes that are different formats of the same value are in sync,
* including: origin and originArray; pid and pid_url; seriesId and
* seriesId_url. This method will prevent the sourceModel attribute from
* being set here.
*
* @param {string|Object} key - The attribute name to set, or an object of
* attribute names and values to set.
* @param {string|number|Object} val - The value to set the attribute to.
* @param {Object} options - Options to pass to the set() method.
* @see https://backbonejs.org/#Model-set
* @since 2.23.0
*/
set: function (key, val, options) {
try {
if (key == null) return this;
// Handle both `"key", value` and `{key: value}` -style arguments.
let attrs = {};
if (typeof key === "object") {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
// Don't allow setting the sourceModel attribute here.
// TODO: how to handle this better?
delete attrs.sourceModel;
// If the title attribute is being set, then format it first
if (Object.keys(attrs).includes("title")) {
attrs.title = this.formatTitle(attrs.title);
}
// Ensure origin and originArray contain the same data, with preference
// given to originArray. If originArray has content, then overwrite
// origin with the a string created from originArray. If *only* origin
// has content, then overwrite originArray with an array created from
// origin.
if (
Object.keys(attrs).includes("originArray") ||
Object.keys(attrs).includes("origin")
) {
const strToArray = this.originToArray(attrs.origin);
const arrayToStr = this.originArrayToString(attrs.originArray);
if (!!arrayToStr) {
attrs.origin = arrayToStr;
} else {
attrs.originArray = strToArray;
}
}
// Ensure that the pid_url and seriesId_url attributes match the pid and
// seriesId attributes being set, and vice versa. If they don't match,
// then set them to the correct values. Prefer the content of the IDs
// over the URLs.
const idToUrlAttrs = [
{ id: "pid", url: "pid_url" },
{ id: "seriesId", url: "seriesId_url" },
];
idToUrlAttrs.forEach(({ id, url }) => {
if (
Object.keys(attrs).includes(id) ||
Object.keys(attrs).includes(url)
) {
if (!!attrs[id] && !attrs[url]) {
attrs[url] = this.DOItoURL(attrs[id]);
} else if (!!attrs[url] && !attrs[id]) {
attrs[id] = this.URLtoDOI(attrs[url]);
} else if (!!attrs[id] && !!attrs[url]) {
attrs[url] = this.DOItoURL(attrs[id]);
}
}
});
// If citationMetadata is being changed, remove old listeners and add
// new ones
if (Object.keys(attrs).includes("citationMetadata")) {
if (this.citationMetadata) {
this.stopListening(this.citationMetadata);
}
if (attrs.citationMetadata && attrs.citationMetadata.length) {
this.listenTo(
attrs.citationMetadata,
"update",
this.trigger.bind(this, "change"),
);
}
}
// Set modified attributes in the regular Backbone way
Backbone.Model.prototype.set.call(this, attrs, options);
} catch (error) {
console.log(
"Error in custom set() method on CitationModel. Will attempt to set" +
" using with Backbone set(). Attributes and error stack trace:",
{ key, val, options },
error,
);
Backbone.Model.prototype.set.call(this, key, val, options);
}
},
/**
* Sets the sourceModel attribute and calls the method to populate the
* Citation Model with the sourceModel attributes. Also removes any existing
* listeners on the previous sourceModel and readds them to the new
* sourceModel. Use this method to set or change the sourceModel attribute.
* @param {Backbone.Model} newSourceModel - The new sourceModel
* @since 2.23.0
*/
setSourceModel(newSourceModel) {
try {
newSourceModel =
newSourceModel && newSourceModel.type == "Package"
? newSourceModel.getMetadata()
: newSourceModel;
// Remove any existing listeners on the previous sourceModel
const currentSourceModel = this.get("sourceModel");
if (currentSourceModel) {
this.stopListening(currentSourceModel);
const creators = currentSourceModel.get("creator") || [];
creators.forEach((creator) => this.stopListening(creator), this);
}
// Add listeners to the new sourceModel
if (newSourceModel) {
const creatorEvents =
"change:individualName change:organizationName change:positionName";
const sourceModelEvents =
"change:origin change:creator change:pubDate change:dateUploaded change:title change:seriesId change:id change:datasource";
const creators = newSourceModel.get("creator") || [];
this.listenTo(newSourceModel, sourceModelEvents, () => {
this.setSourceModel(newSourceModel);
});
creators.forEach((creator) => {
this.listenTo(creator, creatorEvents, () => {
this.setSourceModel(newSourceModel);
});
});
}
Backbone.Model.prototype.set.call(
this,
"sourceModel",
newSourceModel,
);
this.populateFromModel(newSourceModel);
} catch (error) {
console.log("Error in CitationModel.setSourceModel(). Error:", error);
}
},
/**
* Do not call this method directly. Instead, call setSourceModel(), which
* will update listeners and then call this method. This method will
* populate this citation model's attributes from another model, such as a
* SolrResult model or a DataONEObject model. This will reset and overwrite
* any existing attributes on this model.
* @param {Backbone.Model} model - The model to populate from, accepts
* SolrResult or a model that is a DataONEObject or an extended
* DataONEObject. If no model is passed, then the model will be reset to the
* default attributes.
* @since 2.23.0
*/
populateFromModel: function (newSourceModel) {
try {
// Populate this model from the new sourceModel
const newAttrs = this.defaults();
if (!newSourceModel) {
this.set(newAttrs);
return;
}
const attrGetters = this.attrGetters();
Object.entries(attrGetters).forEach(([attrName, getter]) => {
const attrValue = getter.call(this, newSourceModel);
if (attrValue) newAttrs[attrName] = attrValue;
});
this.set(newAttrs);
} catch (error) {
console.log(
"Error populating a CitationModel from the model: ",
newSourceModel,
" Error: ",
error,
);
}
},
/**
* Get the year from the sourceModel. First look for pubDate, then
* dateUploaded (both in SolrResult & ScienceMetadata/EML models). Lastly
* check datePublished (found in ScienceMetadata/EML models only.)
* @param {Backbone.Model} sourceModel - The model to get the year from
* @returns {Number} - The year
* @since 2.23.0
*/
getYearFromSourceModel(sourceModel) {
try {
const year =
this.yearFromDate(sourceModel.get("pubDate")) ||
this.yearFromDate(sourceModel.get("dateUploaded")) ||
this.yearFromDate(sourceModel.get("datePublished"));
return year;
} catch (error) {
console.log(
"Error getting year from the sourceModel. Model and error:",
sourceModel,
error,
);
return this.defaults().year_of_publishing;
}
},
/**
* Get the title from the sourceModel
* @param {Backbone.Model} sourceModel - The model to get the title from
* @returns {String} - The title
* @since 2.23.0
*/
getTitleFromSourceModel(sourceModel) {
try {
let title = sourceModel.get("title");
title = Array.isArray(title) ? title[0] : title;
// If this is a Data object, there may not be a title, so try to get the
// title from the file name
if (!title && sourceModel.get("fileName")) {
let fn = sourceModel.get("fileName");
const extRegex = /\.[^/.]+$/;
// Save the extension
let ext = fn ? fn.match(extRegex) : null;
// remove the period and make it all uppercase
ext = ext ? ext[0].replace(".", "").toUpperCase() : ext;
// Remove the extension and replace underscores with spaces
fn = fn.replace(extRegex, "").replace(/_+/g, " ");
title = fn ? fn : title;
title = title && ext ? title + " [" + ext + "]" : title;
}
return title;
} catch (error) {
console.log(
"Error getting title from the sourceModel. Model and error:",
sourceModel,
error,
);
return this.defaults().title;
}
},
/**
* Get the journal (datasource/node) from the sourceModel. If there is a
* datasource attribute on the sourceModel, then get the name of the member
* node that has that datasource ID. If we can't find a member node that
* matches the datasource, then check if the datasource is the current node.
* If it is, then use the repository name. If there is no datasource
* attribute, then use the current member node's name.
* @param {Backbone.Model} sourceModel - The model to get the journal from
* @returns {String} - The journal
* @since 2.23.0
*/
getJournalFromSourceModel(sourceModel) {
try {
let journal = null;
const datasource = sourceModel.get("datasource");
const mn = MetacatUI.nodeModel.getMember(datasource);
const currentMN = MetacatUI.nodeModel.get("currentMemberNode");
if (datasource) {
if (mn) {
journal = mn.name;
} else if (datasource == MetacatUI.appModel.get("nodeId")) {
journal = MetacatUI.appModel.get("repositoryName");
}
}
if (!journal && currentMN) {
const mnCurrent = MetacatUI.nodeModel.getMember(currentMN);
journal = mnCurrent ? mnCurrent.name : null;
}
return journal;
} catch (error) {
console.log(
"Error getting journal from the sourceModel. Model and error:",
sourceModel,
error,
);
return this.defaults().journal;
}
},
/**
* Get the array of authors ("origin") from the sourceModel. First look for
* creator (EML), then origin (science metadata & solr results), then
* rightsHolder & submitter (base D1 object model). Convert EML parties to
* strings & check for incorrectly escaped characters.
* @param {Backbone.Model} sourceModel - The model to get the originArray
* from
* @returns {Array} - The originArray
* @since 2.23.0
*/
getOriginArrayFromSourceModel(sourceModel) {
try {
// AUTHORS
let authors =
// If it's an EML document, there will be a creator field
sourceModel.get("creator") ||
// If it's a science metadata model or solr results, use origin
sourceModel.get("origin") ||
"";
// otherwise, this is probably a base D1 object model. Don't use
// rightsHolder or submitter for now, because it might not always be the
// author.
// sourceModel.get("rightsHolder") ||
// sourceModel.get("submitter");
// Convert EML parties to strings & check for incorrectly escaped
// characters
if (authors) {
authors = Array.isArray(authors) ? authors : [authors];
authors = authors.map((author) => this.formatAuthor(author));
}
return authors;
} catch (error) {
console.log(
"Error getting originArray from the sourceModel. Model and error:",
sourceModel,
error,
);
return this.defaults().originArray;
}
},
/**
* Get the pid from the sourceModel. First look for id, then identifier.
* @param {Backbone.Model} sourceModel - The model to get the pid from
* @returns {String} - The pid
* @since 2.23.0
*/
getPidFromSourceModel(sourceModel) {
try {
const pid =
sourceModel.get("id") || sourceModel.get("identifier") || null;
return pid;
} catch (error) {
console.log(
"Error getting the pid from the sourceModel. Model and error:",
sourceModel,
error,
);
return this.defaults().pid;
}
},
/**
* Get the seriesId from the sourceModel. Simply looks for the seriesId
* attribute.
* @param {Backbone.Model} sourceModel - The model to get the seriesId from
* @returns {String} - The seriesId
* @since 2.23.0
*/
getSeriesIdFromSourceModel(sourceModel) {
try {
const seriesId = sourceModel.get("seriesId") || null;
return seriesId;
} catch (error) {
console.log(
"Error getting the seriesId from the sourceModel. Model and error:",
sourceModel,
error,
);
return this.defaults().seriesId;
}
},
/**
* Use the sourceModel's createViewURL() method to get the viewUrl for the
* citation. This method is built into DataONEObject models, SolrResult
* models, as well as Portal models. If the sourceModel doesn't have a
* createViewURL() method, then use the default viewUrl (null)
* @param {Backbone.Model} sourceModel - The model to get the viewUrl from
* @returns {String} - The viewUrl, or null if the sourceModel doesn't have
* a createViewURL() method.
* @since 2.23.0
*/
getViewUrlFromSourceModel(sourceModel) {
try {
if (sourceModel && sourceModel.createViewURL) {
return sourceModel.createViewURL();
} else {
return this.defaults().viewUrl;
}
} catch (error) {
console.log(
"Error getting the viewUrl from the sourceModel. Model and error:",
sourceModel,
error,
);
return this.defaults().viewUrl;
}
},
/**
* Format an individual author for display within a citation.
* @param {string|EMLParty} author The author to format
* @returns {string} Returns the author as a string if it was an EMLParty
* with any incorrectly escaped characters corrected.
*/
formatAuthor: function (author) {
try {
// Update the origin array asynchonously if the author is an ORCID
if (this.isOrcid(author)) this.originArrayFromOrcid(author);
// If author is an EMLParty model, then convert it to a string with
// given name + sur name, or organization name
if (typeof author.toCSLJSON === "function") {
author = author.toCSLJSON();
} else if (typeof author === "string") {
author = this.nameStrToCSLJSON(author);
}
return author;
} catch (error) {
console.log(
"There was an error formatting an author, returning " +
"the author input as is.",
error,
);
return author;
}
},
/**
* Cleans up the title for display within a citation. Removes a period from
* the end of the title if it exists and trims whitespace.This method is
* called any time a title is set on the Citation model.
* @param {string} title The title to format
* @returns {string} Returns the title with a period removed from the end if
* it exists.
* @since 2.23.0
*/
formatTitle: function (title) {
if (!title) return "";
return title.replace(/\.+$/, "").trim();
},
/**
* Cleans up the metrics service string for display within a citation.
* Replaces "NULL" with an empty string, removes a period from the end of
* the string if it exists, removes curly braces, and trims whitespace.
* @param {string} str The metrics service string to format
* @returns {string} Returns the metrics service string with "NULL" replaced
* with an empty string.
* @since 2.23.0
*/
formatMetricsServiceString: function (str) {
if (!str) return "";
// The metrics service returns "NULL" if there is no data
str = str === "NULL" ? "" : str;
// Replace period at the end of the string
str = str.replace(/\.+$/, "");
// Remove curly braces
str = str.replace(/{|}/g, "");
// Remove any leading or trailing whitespace
str = str.trim();
// Check for incorrectly escaped characters, like &
const doc = new DOMParser().parseFromString(str, "text/html");
str = doc.body.textContent || "";
return str;
},
/**
* Convert the author string that is returned from the metrics service into
* CSL JSON format. Author strings that come from the metrics service take
* many formats, which might include full given and last names, middle
* initials, first initials, etc. Here are a few example strings: "Chelsea
* Wegner Koch", "Lee W. Cooper", "J. Wiktor", "Sei-Ichi Saitoh", "William
* K. W. Li", "J.R. Lovvorn". Last name prefixes like "van" or "de" are
* stored as a "non-dropping particle". See:
* {@link https://citeproc-js.readthedocs.io/en/latest/csl-json/markup.html#name-variables}
*
* @param {str} author The author string to convert
* @returns {Object} Returns an object with the author's name in CSL JSON
* format.
* @since 2.23.0
*/
nameStrToCSLJSON: function (str) {
if (!str) return null;
const name = {};
str = this.formatMetricsServiceString(str);
// If the string contains one comma, then assume it is in the format "last
// name, first name". Move the first name to the front of the string.
if (str.split(",").length == 2) {
const parts = str.split(",");
str = parts[1].trim() + " " + parts[0].trim();
}
const parts = str
.trim()
.split(/\s+|\./)
.filter((part) => part !== "");
if (parts.length === 1) {
name.literal = str;
return name;
}
// Assume the last word is the family name
name.family = parts.pop();
// Any remaining lowercase words are assumed to be non-dropping particles
const nonDroppingParticles = parts.filter((part) =>
part.match(/^[a-z]+$/),
);
if (nonDroppingParticles.length > 0) {
name["non-dropping-particle"] = nonDroppingParticles.join(" ");
}
// Any remaining words are assumed to be given names
const givenNames = parts.filter((part) => !part.match(/^[a-z]+$/));
if (givenNames.length > 0) {
name.given = givenNames.join(" ");
}
return name;
},
/**
* Given a date, extract the year as a number.
* @param {Date|String|Number} date The date to extract the year from
* @returns {Number} Returns the year as a number, or null if the date is
* invalid.
* @since 2.23.0
*/
yearFromDate: function (date) {
try {
if (!date) return null;
// If Date is already a year (Number object with 4 digits), return it
if (Number.isInteger(date) && date.toString().length == 4) {
return date;
}
// If it is a string with 4 digits, return it as an integer. Use regex.
if (typeof date == "string" && /^\d{4}$/.test(date)) {
return parseInt(date);
}
// Check if the date is a Date object
if (!(date instanceof Date)) {
date = new Date(date);
}
const yr = date.getUTCFullYear();
return yr == "NaN" ? null : yr;
} catch (error) {
console.log(
"There was an error getting the year from the date, returning null.",
error,
);
return null;
}
},
/**
* Check if a string is a valid ORCID.
* @param {string} orcid The ORCID to check
* @returns {boolean} Returns true if the ORCID is valid, false otherwise
* @since 2.23.0
*/
isOrcid: function (orcid) {
try {
if (!orcid) return false;
const regex = new RegExp(
"^https?:\\/\\/orcid.org\\/(\\d{4}-){3}(\\d{3}[0-9X])$",
);
return regex.test(orcid);
} catch {
return false;
}
},
/**
* Use the App Lookup model's get Accounts method to get the name of the
* author from their ORCID, then asynchronously set the originArray to
* contain that name.
* @param {string} orcid The ORCID to get the name for
* @since 2.23.0
*/
originArrayFromOrcid: function (orcid) {
try {
const request = { term: orcid };
const model = this;
const callback = function (response) {
let name = null;
if (response) {
if (Array.isArray(response)) {
const label = response[0].label;
if (label) {
// Name is the format "Min Liew
// (http://orcid.org/0000-0002-5156-4610)" We want to return
// "Min Liew". It will always be two spaces and a "("
name = label.split(" (")[0];
}
}
}
if (name) {
console.log("Setting originArray to ", [name]);
model.set("originArray", [name]);
}
};
MetacatUI.appLookupModel.getAccountsAutocomplete(request, callback);
} catch (error) {
console.log(
"There was an error getting the name from the orcid.",
error,
);
}
},
/**
* Checks if the citation is for a DataONE object from a specific node (e.g.
* PANGAEA)
* @param {string} node - The node id to check, e.g. "urn:node:PANGAEA"
* @returns {boolean} - True if the citation is for a DataONE object from
* the given node
* @since 2.23.0
*/
isFromNode: function (node) {
try {
const sourceModel = this.get("sourceModel");
return (
sourceModel &&
sourceModel.get &&
sourceModel.get("datasource") &&
sourceModel.get("datasource") === node
);
} catch (error) {
console.log(
`There was an error checking if the citation is from node ${node}.` +
`Returning false.`,
error,
);
return false;
}
},
/**
* Convert the comma-separated origin string to an array of authors.
* @param {string} origin - The origin string to convert to an array. If a
* falsy value is passed in, then the default originArray attribute of the
* model is returned.
* @returns {Array} - An array of authors
* @since 2.23.0
*/
originToArray: function (origin) {
try {
if (!origin) {
return this.defaults().originArray;
}
let originArray = origin ? origin.split(", ") : [];
return originArray.map((author) => this.formatAuthor(author));
} catch (error) {
console.log(
"There was an error converting the origin string to an array.",
error,
);
return this.defaults().originArray;
}
},
/**
* Convert the origin array to a string.
* @returns {string} - The origin string. If a falsy value is passed in,
* then the default origin attribute of the model is returned.
* @since 2.23.0
*/
originArrayToString: function (originArray) {
try {
if (!originArray || !originArray.length) {
return this.defaults().origin;
}
// Each author in the array is a CSL JSON object. Map it to a string
// with the author's name in the format First Particle Last or Literal.
// Separate each author name with a comma and a space.
const origin = originArray
.map((a) => {
if (!a) return null;
const ndp = a["non-dropping-particle"];
let name =
(a.given ? a.given + " " : "") +
(ndp ? ndp + " " : "") +
(a.family ? a.family : "");
return (name || a.literal || "").trim();
})
.filter((a) => a)
.join(", ");
return origin;
} catch (error) {
console.log(
"There was an error converting the origin array to a string.",
error,
);
return this.defaults().origin;
}
},
/**
* Returns true if the citation is for a DataONE object that has been
* archived and archived content is not available in the search index.
* @returns {boolean} - True if the citation has no content because it is
* archived and archived content is not indexed.
* @see AppModel#archivedContentIsIndexed
* @since 2.23.0
*/
isArchivedAndNotIndexed: function () {
return this.isArchived() && !this.archivedContentIsIndexed();
},
/**
* Checks if the object being cited is archived, according to the `archived`
* attribute of the source model.
* @returns {boolean} - True if the source model has an `archived` attribute
* that is true.
* @since 2.23.0
*/
isArchived: function () {
return (
this.sourceModel &&
this.sourceModel.get &&
this.sourceModel.get("archived")
);
},
/**
* Checks if archived content is available in the search index.
* @see AppModel#archivedContentIsIndexed
* @returns {boolean} - True if archived content is available in the search
* index.
* @since 2.23.0
*/
archivedContentIsIndexed: function () {
return MetacatUI.appModel.get("archivedContentIsIndexed");
},
/**
* Remove all DOI prefixes from a DOI string, including https, http, doi.org,
* dx.doi.org, and doi:.
* @param {string} str - The DOI string to remove prefixes from.
* @returns {string} - The DOI string without any prefixes.
* @since 2.23.0
*/
removeAllDOIPrefixes: function (str) {
if (!str) return "";
// Remove https and http prefixes
str = str.replace(/^(https?:\/\/)?/, "");
// Remove domain prefixes, like doi.org and dx.doi.org
str = str.replace(/^(doi\.org\/|dx\.doi\.org\/)/, "");
// Remove doi: prefix
str = str.replace(/^doi:/, "");
return str;
},
/**
* Check if a string is a valid DOI.
* @param {string} doi - The string to check.
* @returns {boolean} - True if the string is a valid DOI, false otherwise.
* @since 2.23.0
*/
isDOI: function (str) {
return MetacatUI.appModel.isDOI(str);
},
/**
* Get the URL for the online location of the object being cited when it has
* a DOI. If the DOI is not passed to the function, or if the string is not
* a DOI, then an empty string is returned.
* @param {string} [str] - The DOI string with or without the "doi:" prefix.
* It may already be a URL, or it may be a DOI string. It also handles the
* case where the DOI is already a URL.
* @returns {string} - The DOI URL
* @since 2.23.0
*/
DOItoURL: function (str) {
return MetacatUI.appModel.DOItoURL(str);
},
/**
* Get the DOI from a DOI URL. The URL can be http or https, can include the
* "doi:" prefix or not, and can use "dx.doi.org" or "doi.org" as the
* domain. If a string is not passed to the function, or if the string is
* not for a DOI URL, then an empty string is returned.
* @param {string} url - The DOI URL
* @returns {string} - The DOI string, including the "doi:" prefix
* @since 2.23.0
*/
URLtoDOI: function (url) {
return MetacatUI.appModel.URLtoDOI(url);
},
/**
* Checks if the citation has a DOI in the seriesId or pid attributes.
* @returns {string} - The DOI of the seriesID, if it is a DOI, or the DOI
* of the pid, if it is a DOI. Otherwise, returns null.
* @since 2.23.0
*/
findDOI: function () {
try {
if (!this.sourceModel || !this.sourceModel.isDOI) return null;
const seriesID = this.get("seriesId");
const pid = this.get("pid");
if (this.sourceModel.isDOI(seriesID)) return seriesID;
if (this.sourceModel.isDOI(pid)) return pid;
return null;
} catch (error) {
console.log(
"There was an error finding the DOI for the citation. Returning null",
error,
);
return null;
}
},
/**
* Checks if the citation has a DOI in the seriesId or pid attributes.
* @returns {boolean} - True if the citation has a DOI
* @since 2.23.0
*/
hasDOI: function () {
return this.findDOI() ? true : false;
},
/**
* If this citation has a source model, and if that source model is a
* DataONEObject, then return the results of the DataONEObject's
* `getUploadStatus` function
* @returns {string} - The upload status of the source model, if it is a
* DataONEObject, or null if it is not.
* @since 2.23.0
* @see DataONEObject#getUploadStatus
*/
getUploadStatus: function () {
return this.sourceModel ? this.sourceModel.get("uploadStatus") : null;
},
/**
* Get the URL for the citation. This will check the model for the following
* attributes and return the first that is not empty: view_url, source_url,
* sid_url, pid_url.
* @returns {string} Returns the URL for the citation or an empty string.
* @since 2.23.0
*/
getURL: function () {
const urlSources = ["view_url", "source_url", "sid_url", "pid_url"];
for (let i = 0; i < urlSources.length; i++) {
const url = this.get(urlSources[i]);
if (url) return url;
}
return "";
},
/**
* Get the main identifier for the citation. This will check the model for
* the following attributes and return the first that is not empty: pid,
* seriesId, source_url.
* @returns {string} Returns the main identifier for the citation or an
* empty string.
* @since 2.23.0
*/
getID: function () {
const idSources = ["pid", "seriesId", "source_url"];
for (let i = 0; i < idSources.length; i++) {
const id = this.get(idSources[i]);
if (id) return id;
}
return "";
},
},
);
return Citation;
});