index.js

'use strict';
const assert = require('assert');
const util = require('util');
const _ = require('lodash');

/**
 * @class
 * @classdesc Gathers all the methods in the module
 * @alias PublicationsManager
 */
module.exports = class PublicationsManager {

    /**
     * Class constructor. Receives only two parameters, the raw publications and author data.
     * @constructor
     * @param {Object} authorData The raw data of the author received from publications, in [this format](assets/author-data.json)
     * @param {Object} publicationsData The raw data received from publications, in [this format](assets/publications-data.json)
     * @throws {AssertionError} Throws an error if publicationsData is not defined
     */
    constructor(authorData, publications) {
        assert.notStrictEqual(util.isNullOrUndefined(publications), true, "publications can't be Null or Undefined");
        assert.strictEqual(publications instanceof Object, true, "publications must be an object");
        if (!util.isNullOrUndefined(authorData)) {
            assert.strictEqual(authorData instanceof Object, true, "authorData must be an object");
        }
        this.publications = publications;
        this.authorData = authorData;
    }

    /**
     * Returns an array with the publications found in the input range, grouped by year. The output array has the following format:
     * ```
     * [
     *  {"year": 2014, "publications": [...{publicationsRawDataFragment}]},
     *  {"year": 2015, "publications": [...{publicationsRawDataFragment}]},
     *  {"year": 2016, "publications": [...{publicationsRawDataFragment}]},
     * ]
     * ```
     * Each `publicationsRawDataFragment` contains all the data from each publication
     * @method getPublicationsPerYear
     * @memberof PublicationsManager
     * @param {Number} from The year from which start to look for publications
     * @param {Number} to The year to end looking for publications
     * @returns {Array} Array with the publications found, grouped by year
     * @instance
     * @throws {AssertionError} Throws an error if the range is not valid (i.e. the starting year is greater than the ending year)
     */
    getPublicationsPerYear(from, to) {
        assert(typeof (from) === 'number' && typeof (to) === 'number', "Both of the years must be numbers");
        assert(from <= to, "Starting year must be greater or equal than ending year");

        // Initialize the resulting object with all the groups by year, empty
        let publicationsObject = {};
        _.range(from, to + 1).forEach((year) => {
            publicationsObject[year] = [];
        });

        // For each publication
        this.publications.forEach((publication => {
            // Get its date
            const publicationDate = this.__getPublicationDate(publication);

            // If the publication is in the range passed as parameter
            if (publicationDate
                && publicationDate.getFullYear() <= to
                && publicationDate.getFullYear() >= from) {

                // Include the publication in its year group
                publicationsObject[publicationDate.getFullYear()].push(publication);

            }
        }));

        // Transform the object with format <year>: [publications] to an array
        // of objects of publications grouped by year
        const resultObject = Object.keys(publicationsObject).map(key => {
            return { year: parseInt(key), publications: publicationsObject[key] };
        });

        return resultObject;
    }

    /**
     * Returns an array with the publications found in the input range, ungrouped. The output array has the following format:
     * ```
     * [...{publicationsRawDataFragment}]
     * ```
     * Each `publicationsRawDataFragment` contains all the data from each publication
     * @method getPublicationsInRange
     * @memberof PublicationsManager
     * @param {Number} from The year from which start to look for publications
     * @param {Number} to The year to end looking for publications
     * @returns {Array} Array with the publications found, ungrouped
     * @throws {AssertionError} Throws an error if the range is not valid (i.e. the starting year is greater than the ending year)
     * @instance
     */
    getPublicationsInRange(from, to) {
        // All the arguments assertions are made inside getPublicationsPerYear

        const publicationsGrouped = this.getPublicationsPerYear(from, to);

        // Map the JSON to an array of arrays
        let ungrouped = publicationsGrouped.map(group => { return group.publications; });

        // Flatten the array
        ungrouped = [].concat.apply([], ungrouped);

        return ungrouped;
    }

    /**
     * Returns an array with every publication citation number, grouped by year. The output array has the following format:
     * ```
     * [...{
     *  "year": 2014,
     *  "cites": [...Number]
     * }]
     * ```
     * The `cites` property contains an array representing the number of cites of each publication
     * @method getCitesPerYear
     * @memberof PublicationsManager
     * @param {Number} from The year from which start to look for citations
     * @param {Number} to The year to end looking for citations
     * @returns {Array} Array with the citations found, grouped by year
     * @throws {AssertionError} Throws an error if the range is not valid (i.e. the starting year is greater than the ending year)
     * @instance
     */
    getCitesPerYear(from, to) {
        // All the arguments assertions are made inside getPublicationsPerYear

        let publicationsGrouped = this.getPublicationsPerYear(from, to);

        // Transform the array of publications of each year to an array of numbers, representing
        // each publication cite number
        publicationsGrouped = publicationsGrouped.map(group => {
            return {
                year: group.year,
                cites: group['publications'].map(
                    publication => {
                        // If citedby-count is not present return 0 instead of undefined or NaN
                        return parseInt(publication['citedby-count']) || 0;
                    }
                )
            };
        });

        return publicationsGrouped;
    }

    /**
     * Returns the last year in which an researcher published
     * @method getLastPublicationYear
     * @memberof PublicationsManager
     * @returns {Number?} The last year in which the researcher published or null if there are no years
     * @instance
     */
    getLastPublicationYear() {
        const years = this.publications.map(publication => {
            const date = this.__getPublicationDate(publication);
            return date ? date.getFullYear() : null;
        }).filter(year => {
            return year !== null;
        });

        // This prevents from returning -Infinity
        if (years.length === 0) {
            return null;
        } else {
            return Math.max.apply(Math, years);
        }
    }

    /**
     * Returns the array of publications scopus ids
     * @method getScopusIds
     * @memberof PublicationsManager
     * @returns {Array} Array of the publications scopus ids
     * @instance
     */
    getScopusIds() {
        return this.publications.map(p => p['dc:identifier'].split('SCOPUS_ID:')[1]);
    }

    /**
     * Returns the SCOPUS ID of a publication
     * @method getPublicationScopusId
     * @memberof PublicationsManager
     * @param {Object} rawPublication The raw publication from which you want to extract the SCOPUS_ID
     * @returns {String} The publication SCOPUS ID
     * @instance
     */
    getPublicationScopusId(rawPublication) {
        return rawPublication['dc:identifier'].split('SCOPUS_ID:')[1];
    }

    /**
     * Returns a string with publication title of given scopus id
     * @method getPublicationTitle
     * @memberof PublicationsManager
     * @param {String} scopusId The scopus id of desired publication
     * @returns {String} Publication title of the publication object
     * @instance
     */
    getPublicationTitle(scopusId) {
        let publication = this.getPublication(scopusId);
        return publication["dc:title"];
    }

    /**
     * Returns a Date object with publication date of given scopus id
     * @method getPublicationDate
     * @memberof PublicationsManager
     * @param {String} scopusId The scopus id of desired publication
     * @returns {Date} Publication date of the publication object
     * @instance
     */
    getPublicationDate(scopusId) {
        let publication = this.getPublication(scopusId);
        return this.__getPublicationDate(publication);
    }

    /**
     * Returns a Date object with publication date of given publication
     * @method getPublicationDateFromPublication
     * @memberof PublicationsManager
     * @param {Object} publication The publication object
     * @returns {Date} Publication date of the publication object
     * @instance
     */
    getPublicationDateFromPublication(publication) {
        this.__isPublication(publication);
        return this.__getPublicationDate(publication);
    }

    /**
     * Returns a string with publication DOI of given scopus id
     * @method getPublicationDOI
     * @memberof PublicationsManager
     * @param {String} scopusId The scopus id of desired publication
     * @returns {String} Publication DOI of the publication object
     * @instance
     */
    getPublicationDOI(scopusId) {
        let publication = this.getPublication(scopusId);
        return publication["prism:doi"];
    }

    /**
     * Returns an integer with maximun publication percentile of given scopus id
     * @method getPublicationPercentile
     * @memberof PublicationsManager
     * @param {String} scopusId The scopus id of desired publication
     * @returns {String} Publication percentile of the publication object
     * @instance
     */
    getPublicationPercentile(scopusId) {
        let publication = this.getPublication(scopusId);
        return Number(publication["percentile"]);
    }

    /**
     * Returns an object with the three fields about the publication category of given scopus id:
     * ```
     *  {
     *      "aggregationType": "Journal",
     *      "subtype": "ar",
     *      "subtypeDescription": "Article"
     *  }
     * ```
     * @method getPublicationType
     * @memberof PublicationsManager
     * @param {String} scopusId The scopus id of the publication
     * @returns {Object} Three fields of publication category
     * @instance
     */
    getPublicationType(scopusId) {
        let publication = this.getPublication(scopusId);
        return {
            "aggregationType": publication["prism:aggregationType"],
            "subtype": publication["subtype"],
            "subtypeDescription": publication["subtypeDescription"]
        };
    }

    /**
     * Returns a unique publication category given its scopus id
     * @method getPublicationUniqueType
     * @memberof PublicationsManager
     * @param {String} scopusId The scopus id of the publication
     * @returns {String} The publication category. It can be 'article', 'conference' or 'other'
     * @instance
     */
    getPublicationUniqueType(scopusId) {
        let publication = this.getPublication(scopusId);

        return this.getPublicationUniqueTypeFromPublication(publication);
    }

    /**
     * Returns a unique publication category given a publication object
     * @method getPublicationUniqueTypeFromPublication
     * @memberof PublicationsManager
     * @param {Object} publication The publication object
     * @returns {String} The publication category. It can be 'article', 'conference' or 'other'
     * @instance
     */
    getPublicationUniqueTypeFromPublication(publication) {
        this.__isPublication(publication);

        const aggregationType = String(publication['prism:aggregationType']).toLowerCase();
        const subtypeDescription = String(publication['subtypeDescription']).toLowerCase();

        if (aggregationType === 'journal') {
            return 'article';
        } else if (aggregationType === 'conference proceeding' ||
            (aggregationType === 'book series' && subtypeDescription === 'conference paper')
        ) {
            return 'conference';
        } else {
            return 'other';
        }
    }

    /**
     * Returns the ISSN of the journal where the article was published. This method is intended to be used with articles.
     * For conference papers, it makes not much sense, since conferences ar retrieved for acronym, not ISSN
     * @method getPublicationISSN
     * @memberof PublicationsManager
     * @param {String} scopusId The scopus id of the publication
     * @returns {String} The ISSN of the journal or book, or null if it's not present
     * @instance
     */
    getPublicationISSN(scopusId) {
        let publication = this.getPublication(scopusId);
        return publication['prism:issn'] || null;
    }

    /**
     * Returns the ISSN of the journal where the article was published. This method is intended to be used with articles.
     * For conference papers, it makes not much sense, since conferences ar retrieved for acronym, not ISSN
     * @method getPublicationISSNFromPublication
     * @memberof PublicationsManager
     * @param {Object} publication The publication object
     * @returns {String} The ISSN of the journal or book, or null if it's not present
     * @instance
     */
    getPublicationISSNFromPublication(publication) {
        this.__isPublication(publication);
        return publication['prism:issn'] || null;
    }

    /**
     * Returns the journal name where the paper was published. This method is intended to be used with
     * journal papers. For conferences, it makes not much sense.
     * @method getPublicationJournalName
     * @memberof PublicationsManager
     * @param {String} scopusId The scopus id of the publication
     * @returns {String} The journal name of the publication
     * @instance
     */
    getPublicationJournalName(scopusId) {
        const publication = this.getPublication(scopusId);

        return this.getPublicationJournalNameFromPublication(publication);
    }

    /**
     * Returns the journal name where the paper was published. This method is intended to be used with
     * journal papers. For conferences, it makes not much sense.
     * @method getPublicationJournalName
     * @memberof PublicationsManager
     * @param {Object} publication The publication object
     * @returns {String} The journal name of the publication
     * @instance
     */
    getPublicationJournalNameFromPublication(publication) {
        assert(this.getPublicationUniqueTypeFromPublication(publication) !== 'conference', 'The publication is not an article');

        return publication['prism:publicationName'];
    }

    /**
     * Returns the conference name where the paper was published. This method is intended to be used with
     * conference papers. For articles, it makes not much sense.
     * @method getPublicationConfName
     * @memberof PublicationsManager
     * @param {String} scopusId The scopus id of the publication
     * @returns {String} The conference name of the publication, or null if it's not present
     * @instance
     */
    getPublicationConfName(scopusId) {
        let publication = this.getPublication(scopusId);
        let confName;
        try {
            confName = publication.abstract.item.bibrecord.head.source["additional-srcinfo"].conferenceinfo.confevent.confname;
        } catch (e) {
            confName = null;
        }
        return confName;
    }

    /**
     * Returns the conference name where the paper was published. This method is intended to be used with
     * conference papers. For articles, it makes not much sense.
     * @method getPublicationConfNameFromPublication
     * @memberof PublicationsManager
     * @param {Object} publication The publication object
     * @returns {String} The conference name of the publication, or null if it's not present
     * @instance
     */
    getPublicationConfNameFromPublication(publication) {
        this.__isPublication(publication);
        let confName;
        try {
            confName = publication.abstract.item.bibrecord.head.source["additional-srcinfo"].conferenceinfo.confevent.confname;
        } catch (e) {
            confName = null;
        }
        return confName;
    }

    /**
     * Returns an array with subject areas information in a publication of given scopus id:
     * ```
     * [
     *   {
     *       "@abbrev": "COMP",
     *       "@code": "1712",
     *       "$": "Software",
     *       "@_fa": "true"
     *   },
     *   {
     *       "@abbrev": "COMP",
     *       "@code": "1710",
     *       "$": "Information Systems",
     *       "@_fa": "true"
     *   },
     *   {
     *       "@abbrev": "COMP",
     *       "@code": "1708",
     *       "$": "Hardware and Architecture",
     *       "@_fa": "true"
     *   }
     * ]
     * ```
     * @method getPublicationSubjectAreas
     * @memberof PublicationsManager
     * @param {String} scopusId The scopus id of desired publication
     * @returns {Array} Array with the information about subject areas of the publication
     * @instance
     */
    getPublicationSubjectAreas(scopusId) {
        let publication = this.getPublication(scopusId);
        return publication["abstract"]["subject-areas"]["subject-area"];
    }

    /**
     * Returns an array with some information about authors of a publication of given scopus id:
     * ```
     *  [
	 *		{						
	 *			"@seq": "1",
     *			"@auid": "6507957743",
     *          "name": "Doe, Jane"
	 *		},
	 *		{
	 *			"@seq": "2",
     *			"@auid": "22333454800",
     *          "name": "Doe J."
	 *		},
	 *		{
	 *			"@seq": "3",
     *			"@auid": "24802465400",
     *          "name": "Doe Jane"
	 *		},
	 *		{
	 *		    "@seq": "4",
     *	    	"@auid": "15120180100",
     *          "name": "Doe, Jane"
	 *  	}
	 *	]
     * ```
     * "@seq" is the number that each author hold.
     * "@auid" is the authorId of each author.
     * @method getPublicationsAuthors
     * @memberof PublicationsManager
     * @param {String} scopusId The scopus id of desired publication
     * @returns {Array} Array with authors information
     * @instance
     */
    getPublicationsAuthors(scopusId) {
        let publication = this.getPublication(scopusId);
        return publication["abstract"]["authors"]["author"].map(a => ({ '@seq': a['@seq'], '@auid': a['@auid'], 'name': a['ce:indexed-name'] }));
    }

    /**
     * Returns an integer representing the number of cites of the publication with the given scopusId
     * @method getPublicationCites
     * @memberof PublicationsManager
     * @param {String} scopusId The scopus id of desired publication
     * @return {Number} Number of cites
     * @instance
     */
    getPublicationCites(scopusId) {
        const publication = this.getPublication(scopusId);

        return Number(publication['citedby-count']) || 0;
    }

    /**
     * Returns a number with the publication cite score in the year it was published, or the last
     * year with data
     * @method getPublicationCiteScore
     * @memberof PublicationsManager
     * @param {String} scopusId The scopus id of desired publication
     * @return {Number} Publication's cite score
     * @instance
     */
    getPublicationCiteScore(scopusId) {
        const publication = this.getPublication(scopusId);

        const publicationDate = this.__getPublicationDate(publication);

        let res = null;

        // If the publication has no date, the citeScore is null
        if (publicationDate && _.has(publication, 'publisherStats.citeScoreYearInfoList.citeScoreYearInfo')) {
            // We retrieve all the citeScore info about the publication and its year
            const citeScoreList = publication.publisherStats.citeScoreYearInfoList.citeScoreYearInfo;
            const publicationYear = publicationDate.getFullYear();

            const totalYears = citeScoreList.length;
            let yearsChecked = 0;

            // Start looking for cite score info at the publication year. Stop when the curr year is 1900
            // or when all the years available are checked
            for (let currYear = publicationYear; currYear > 1900 && yearsChecked < totalYears; currYear--) {
                // Get the info about that year
                const citeScore = citeScoreList.find(element => element['@year'] === String(currYear));

                // If there are info in that year and the status is not 'in progress'
                if (citeScore && citeScore['@status'] !== 'In-Progress') {
                    // Retrieve the data and stop the loop
                    res = Number(citeScore['citeScoreInformationList'][0]['citeScoreInfo'][0]['citeScore']);

                    break;
                }
                yearsChecked++;
                // Else, try with the previous year
            }
        }

        return res;
    }

    /**
     * Returns the date in which a publication was printed
     * @method getPublicationPrintDate
     * @memberof PublicationsManager
     * @param {String} scopusId The scopus id of desired publication
     * @return {Date?} Publication's print date of null if the publication hasn't been printed yet
     * @instance
     */
    getPublicationPrintDateFromPublication(publication) {
        let res;

        /* If the publication has both volume and cover date we can
        assume that the publication has been printed*/
        if (_.has(publication, 'prism:volume') &&
            _.has(publication, 'prism:coverDate')) {
            // If the date is in an unrecognized format, Date.parse will return
            // NaN, so we have to return null
            res = Date.parse(publication['prism:coverDate']) || null;
        }

        return res;
    }

    /**
     * Returns the date in which a publication (by scopusId) was printed
     * @method getPublicationPrintDate
     * @memberof PublicationsManager
     * @param {String} scopusId The scopus id of desired publication
     * @return {Date?} Publication's print date of null if the publication hasn't been printed yet
     * @instance
     */
    getPublicationPrintDate(scopusId) {
        const publication = this.getPublication(scopusId);

        return this.getPublicationPrintDateFromPublication(publication);
    }

    /**
     * Returns the volume in which the publication has been published
     * @method getPublicationVolume
     * @memberof PublicationsManager
     * @param {String} scopusId The scopus id of desired publication
     * @return {String?} Publication's volume or null if the publication hasn't been published yet
     * @instance
     */
    getPublicationVolume(scopusId) {
        let res = null;
        const publication = this.getPublication(scopusId);

        if (_.has(publication, 'prism:volume')) {
            res = String(publication['prism:volume']);
        }

        return res;
    }

    /**
     * Returns the publication issue or, if it doesn't have issue, the pages.
     * @method getPublicationIssueOrPages
     * @memberof PublicationsManager
     * @param {String} scopusId The scopus id of desired publication
     * @return {String?} Publication's issue, pages or null if the publication has no issue
     * @instance
     */
    getPublicationIssueOrPages(scopusId) {
        let res = null;
        const publication = this.getPublication(scopusId);

        if (_.has(publication, 'prism:issueIdentifier')) {
            res = String(publication['prism:issueIdentifier']);
        } else if (_.has(publication, 'prism:pageRange')) {
            res = String(publication['prism:pageRange']);
        }

        return res;
    }

    /**
     * Filters the publications to retain only the type passed as parameter.
     * @method filterPublications
     * @memberof PublicationsManager
     * @param {String} publicationsType A string indicating the type of publications to retain.
     * The accepted values are 'article', 'conference' or 'all'
     * @return {PublicationsManager} Returns an object of the same type as this for chaining operations.
     * The class publications attribute is the one that is modified.
     * @throws {AssertionError} Throws an error `publicationsType`is null or undefined or if it's not a valid string
     * @instance
     */
    filterPublications(publicationsType) {
        assert(!util.isNullOrUndefined(publicationsType), 'publicationsType can\'t be null or undefined');
        assert(typeof (publicationsType) === 'string', 'publicationsType must be a string');
        assert(publicationsType === 'conference' || publicationsType === 'article' || publicationsType === 'other'
            || publicationsType === 'all', 'publicationsType has to be article or conference');

        this.publications = this.publications.filter((publication) => {
            if (publicationsType === 'all') {
                return true; //Accept everything if publicationsType is all
            } else {
                return this.getPublicationUniqueTypeFromPublication(publication) === publicationsType;
            }
        });

        return this;
    }

    /**
     * Returns the conference class of an enriched publication, by its scpusId. If the publication 
     * has been published in a journal or it's not enriched, null is returned.
     * @param {Srting} scopusId The publication's scopusId you want to know its conference class
     * @return {String?} The publication's conference class
     */
    getPublicationConferenceClass(scopusId) {
        const publication = this.getPublication(scopusId);

        return this.getPublicationConferenceClassFromPublication(publication);
    }

    /**
     * Returns the conference class of an enriched publication. If the publication has been
     * published in a journal or it's not enriched, null is returned.
     * @param {Object} publication The publication you want to know its conference class
     * @return {String?} The publication's conference class
     */
    getPublicationConferenceClassFromPublication(publication) {
        let res = publication['conferenceClass'] || null;

        if (res !== null) {
            res = String(res);
        }

        return res;
    }

    /**
     * Returns the conference rating of an enriched publication, by its scpusId. If the publication 
     * has been published in a journal or it's not enriched, null is returned.
     * @param {Srting} scopusId The publication's scopusId you want to know its conference rating
     * @return {String?} The publication's conference rating
     */
    getPublicationConferenceRating(scopusId) {
        const publication = this.getPublication(scopusId);

        return this.getPublicationConferenceRatingFromPublication(publication);
    }

    /**
     * Returns the conference rating of an enriched publication. If the publication has been
     * published in a journal or it's not enriched, null is returned.
     * @param {Object} publication The publication you want to know its conference rating
     * @return {String?} The publication's conference rating
     */
    getPublicationConferenceRatingFromPublication(publication) {
        let res = publication['conferenceRating'] || null;

        if (res !== null) {
            res = String(res);
        }

        return res;
    }

    /**
     * Returns the quality source of an enriched publication, by its scpusId.
     * If the publication it's not enriched, null is returned.
     * @param {Srting} scopusId The publication's scopusId you want to know its quality source
     * @return {String?} The publication's quality source
     */
    getPublicationQualitySource(scopusId) {
        const publication = this.getPublication(scopusId);

        return this.getPublicationQualitySourceFromPublication(publication);
    }

    /**
     * Returns the quality source of an enriched publication. If the publication 
     * it's not enriched, null is returned.
     * @param {Object} publication The publication you want to know its quality source
     * @return {String?} The publication's quality source
     */
    getPublicationQualitySourceFromPublication(publication) {
        return publication['qualitySource'] || null;
    }

    /**
     * Remove all duplicated publications in the array of publications.
     * @method removeDuplicates
     * @memberof PublicationsManager
     * @return {PublicationsManager} Returns an object of the same type as this for chaining operations.
     * The class publications attribute is the one that is modified.
     * @instance
     */
    removeDuplicates() {
        this.publications = _.uniqBy(this.publications, "dc:identifier");
        return this;
    }

    /**
     * Returns a publication date in a Javascript Date object, or null if the publication has no date.
     * @method __getPublicationDate
     * @memberof PublicationsManager
     * @param {Object} publication Publication object
     * @returns {Date} Publication date
     * @private
     * @instance
     */
    __getPublicationDate(publication) {
        let publicationDate = null;

        if (_.has(publication, 'abstract.item.bibrecord.head.source.publicationdate')) {
            const publicationsDate = publication.abstract.item.bibrecord.head.source.publicationdate;

            if (!publicationsDate.hasOwnProperty('day')) {
                publicationsDate.day = 1;
            }

            if (!publicationsDate.hasOwnProperty('month')) {
                publicationsDate.month = 1;
            }
            publicationDate = new Date(
                parseInt(publicationsDate.year),
                parseInt(publicationsDate.month) - 1, // In Javascript, January is month 0
                parseInt(publicationsDate.day));
        }

        return publicationDate;
    }

    /**
     * Checks if the parameter passed is a publication. If not, throws assertion error
     * @method __isPublication
     * @memberof PublicationsManager
     * @param {Object} publication Publication object
     * @returns {void} Nothing
     * @throws {AssertionError} Throws an error if the parameter passed is not a publication object
     * @instance
     */
    __isPublication(publication) {
        assert(!util.isNullOrUndefined(publication), "publication should not be null or undefined");
        assert(typeof (publication) === 'object', "publication should be an object");
        assert(_.has(publication, "dc:identifier"), "publication object must have a Scopus ID, aka 'dc:identifier' property");
    }

    /**
     * Returns a publication object given a scopus id.
     * @method getPublication
     * @memberof PublicationsManager
     * @param {String} scopusId Scopus id
     * @returns {Object} publication Publication object
     * @throws {AssertionError} Throws an error there is no given scopus id, it isn't a string or there is no publication with that id.
     * @instance
     */
    getPublication(scopusId) {
        assert(typeof (scopusId) === 'string', "scopusId should be a string");
        scopusId = 'SCOPUS_ID:' + scopusId;
        let publication = this.publications.find(p => p['dc:identifier'] === scopusId);
        assert(!util.isNullOrUndefined(publication), "Publication with scopus id " + scopusId + " not found");

        return publication;
    }
};