NatLibFi/marc-record-validators-melinda

View on GitHub
src/urn.js

Summary

Maintainability
D
1 day
Test Coverage
import fetch from 'node-fetch';
import {isElectronicMaterial} from './utils';
import createDebugLogger from 'debug';

const URN_GENERATOR_URL = 'https://generator.urn.fi/cgi-bin/urn_generator.cgi?type=nbn';

export default function (isLegalDeposit = false, useMelindaTemp = true) {
  const debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:urn');
  const debugData = debug.extend('data');

  // We should check that the f856 with URN has second indicator '0' (Resource), ' ' (No information provided) or '8' (No display constant generated)
  // - if second indicator is '1' (Version of resource) or '2' (Related resource) the URN in f856 is not correct for the resource described in the record

  // This checks only the existence of URNs from the Finnish urn.fi -resolver

  const hasURN = f => f.tag === '856' && f.subfields.some(({code, value}) => code === 'u' && (/urn.fi/u).test(value));

  return {
    description: 'Adds URN for record, to 856-field (if not existing). If isLegalDeposit is active, adds legal deposit subfields to the f856s with URN.',
    validate,
    fix
  };

  async function fix(record) {
    const f856sUrn = record.fields.filter(hasURN);
    debugData(`f856sUrn: ${JSON.stringify(f856sUrn)}`);

    const ldSubfields = isLegalDeposit ? createLDSubfields() : [];
    debugData(`IsLegalDeposit: ${isLegalDeposit}, LegalDepositSubfields: ${JSON.stringify(ldSubfields)}`);

    // We add the URN even if we're not getting the legalDeposit - where does this URN resolve?
    // We probably should not do these additions

    if (f856sUrn.length === 0) { // eslint-disable-line functional/no-conditional-statements
      const {code, value, generated} = await createURNSubfield(record);

      if (generated && useMelindaTemp) {
        const tempSubField = {code: '9', value: 'MELINDA<TEMP>'};

        record.insertField({
          tag: '856',
          ind1: '4',
          ind2: '0',
          subfields: [{code, value}, ...ldSubfields, tempSubField]
        });

        return true;
      }

      record.insertField({
        tag: '856',
        ind1: '4',
        ind2: '0',
        subfields: [{code, value}, ...ldSubfields]
      });

      return true;
    } else if (isLegalDeposit) { // eslint-disable-line functional/no-conditional-statements

      // We add here legal deposit information to all URN-f856s - we probably should not do this
      // We should add extra f856 URN / URNs for legal deposits that already have a open (non-legal-deposit) URN
      // How do we decide which URN to use as a template if there are several URNs
      // We should check for existence of a legal deposit URN anyways

      f856sUrn.forEach(f => {
        // Change phrase from old to new if field with old phrase is found
        if (f.subfields.some(sf => hasOld856LdPhrase(sf))) { // eslint-disable-line functional/no-conditional-statements
          f.subfields // eslint-disable-line functional/immutable-data
            .find(sf => hasOld856LdPhrase(sf))
            .value = 'Käytettävissä vapaakappaletyöasemilla';
        }

        // Create subfields if necessary
        ldSubfields.forEach(ldsf => {
          if (!f.subfields.some(sf => sf.code === ldsf.code && sf.value === ldsf.value && !hasOld856LdPhrase(sf))) { // eslint-disable-line functional/no-conditional-statements
            f.subfields.push(ldsf); // eslint-disable-line functional/immutable-data
          }
        });
      });
    }

    return true;

    // We should check existence of URN in f024 i1: '7' $2 urn/URN for this too

    async function createURNSubfield(rec) {
      // isbn is picked from the last 020 $a in the record
      // what should we do in case of several 020 $a:s
      const isbn = rec.fields.reduce((acc, f) => {
        if (f.tag === '020') {
          const a = f.subfields.find(sf => sf.code === 'a');
          return a ? a.value : undefined;
        }

        return acc;
      }, undefined);

      debugData(`isbns: ${isbn}`);

      const {generated, value} = await createURN(isbn);
      return {code: 'u', value, generated};

      async function createURN(isbn = false) {
        if (isbn) {
          return {generated: false, value: `https://urn.fi/URN:ISBN:${isbn}`};
        }

        const response = await fetch(URN_GENERATOR_URL);
        const body = await response.text();

        // If we generated URN we could also add it to the 024
        // generated 024 should also have $9 MELINDA<TEMP>
        return {generated: true, value: `https://urn.fi/${body}`};
      }
    }

    function hasOld856LdPhrase({code, value}) {
      if (code === 'z' && value === 'Käytettävissä vapaakappalekirjastoissa') {
        return true;
      }

      return false;
    }

  }

  // Later when the new subfields that have f506/f540 -type contents, we should add also them here
  function createLDSubfields() {
    return [
      {
        code: 'z',
        value: 'Käytettävissä vapaakappaletyöasemilla'
      },
      {
        code: '5',
        value: 'FI-Vapaa'
      }
    ];
  }

  function fieldHasLDSubfields(field, ldSubfields) {
    if (ldSubfields.every(ldsf => field.subfields.some(sf => sf.code === ldsf.code && sf.value === ldsf.value))) {
      return true;
    }
  }

  function validateLD(f856sUrn) {
    debug(`Validating the existence of legal deposit subfields`);
    const ldSubfields = createLDSubfields();
    const f856sUrnWithLdSubfields = f856sUrn.filter(field => fieldHasLDSubfields(field, ldSubfields));
    if (f856sUrnWithLdSubfields.length > 0) {
      debug(`Record has ${f856sUrnWithLdSubfields.length} URN fields with all necessary legal deposit subfields`);
      debugData(`f856sUrnWithLdSubfields: ${JSON.stringify(f856sUrnWithLdSubfields)}`);
      return true;
    }
    return false;
  }

  function validate(record) {
    // if not electronic skip this validator
    if (!isElectronicMaterial(record)) {
      debug(`Record is not electronic - no need to validate legal deposit URNs`);
      return {valid: true};
    }

    const f856sUrn = record.fields.filter(hasURN);

    if (f856sUrn.length > 0) {
      debug(`Record has ${f856sUrn.length} URN fields`);
      debugData(`f856sUrn: ${JSON.stringify(f856sUrn)}`);

      if (!isLegalDeposit || validateLD(f856sUrn)) {
        debug(`Record is valid`);
        return {valid: true};
      }
    }
    debug(`No (valid) URN fields - Record is not valid`);
    return {valid: false};
  }
}