MediaWiki:MARCimporterJS.js: mudanças entre as edições

De Wikincat
Ir para navegação Ir para pesquisar
(Sem diferença)

Edição das 10h03min de 15 de fevereiro de 2024

/*jshint esversion: 6 */
function mainFunc() {
  /* Este script é dividido basicamente em 4 partes: um handler para registros
    MARC em ISO 2709, um handler para registros no formato "MARC tags", uma
    configuração para registros bibliográficos e uma configuração para registros
    de autoridade. O script foi baseado no Módulo:MARCimporter, usando Lua.
  */
  // inicializa as variáveis básicas do registro
  let leader = '';
  let baseAddressOfData = 0;
  let directory = 0;
  let dataValuesGroup = '';
  // inicializa as variáveis auxiliares
  let directoryEntry = 0;
  let entryTag = 0;
  let entryDataLen = 0;
  let entryInitPos = 0;
  // Objeto que contém os dados dos campos. Formato:
  // { tag: '', len: '', initPos: '', data: '', ind1: '', ind2: '' }
  let dataField = {};
  // Array que contém cada objeto dataField. Formato: [ {}, {}, {}, {}, {} ]
  let dataFields = [];
  // campos especiais
  let controlField003 = '';
  let controlField006 = '';
  let controlField007 = '';
  let controlField008 = '';
  let dataField040 = '';
  // objeto que contém as configurações do registro bibliográfico
  let bibRecordParams = {};
  // objeto que contém as configurações do registro de autoridade
  let autRecordParams = {};
  // variáveis finais de retorno
  let url = '';
  let fieldQueryString = '';
  let fieldTemplates = '';
  let templates = '';

  function normalizeData(data) {
    const newdata = data.replace(/\|(.)/g, ' $$$1 ') // substitui "|a" por " $a "
      .replace(/(\d)p\./, '$1 p.') // "1p." por "1 p."
      .replace(/(\d)cm/, '$1 cm') // "1cm" por "1 cm"
      .replace(/(\d)ed\./, '$1 ed.') // "1ed." por "1 ed."
      .replace(/\.\s?-$/, '.') // ". -" por "."
      .replace(/(\$.*)(\$w.*)/, '$2 $1') // move o subcampo $w para a frente
      .replace(/(\s\$9\s.*)/, '') // remove o subcampo $9
      .replace(/(\$z.*)(\$u.*)/, '$2 $1') // move o subcampo $u para a frente
      .replace(/[\n\r]/g, ''); // remove line feed e carriage return
    return newdata;
  }

  function isoRecordHandler(isoRec) {
    /* MARC ISO 2709 record handler:
      Em Lua, o MediaWiki substitui caracteres de controle (RS, US, GS) pelo
      caractere de "desconhecido" (losango com interrogação) e, aqui,
      substituo esses caracteres por um pipe (é necessário substituir
      por um caractere da faixa ASCII). Em JavaScript talvez isso não precisasse
      ser feito mas, para manter a mesma lógica do script em Lua, decidi deixar
      assim mesmo.
    */
    let record = isoRec;
    record = record.replace(/\p{Cc}/gu, '|');
    // configuração das variáveis básicas do registro:
    // obtém o líder (Ex.: 00898nam a2200277 a 4500)
    leader = record.substring(0, 25);
    // obtém o endereço base dos dados
    baseAddressOfData = parseInt(leader.substring(12, 17), 10); // Ex.: 00277
    // obtém o diretório (-1 para não pegar RS)
    // Ex: 0010010000000050017000100080041000270200027000680400017000950...
    directory = record.substring(24, baseAddressOfData - 1);
    // obtém os dados dos campos em um único grupo
    dataValuesGroup = record.substring(baseAddressOfData);
    // é necessário transformar a string em uma cadeia de bytes para posterior
    // separação dos dados
    dataValuesGroup = new TextEncoder().encode(dataValuesGroup);
    // enquanto o tamanho do diretório for maior que 0...
    while (directory.length > 0) {
      directoryEntry = directory.substring(0, 12); // 245008100177
      entryTag = directoryEntry.substring(0, 3); // 245
      entryDataLen = parseInt(directoryEntry.substring(3, 7), 10); // 0081
      entryInitPos = parseInt(directoryEntry.substring(7, 13), 10); // 00177

      // cria um objeto com os dados...
      dataField = {
        initPos: entryInitPos,
        len: entryDataLen,
        tag: entryTag,
      };
      // e insere cada objeto dataField na array dataFields
      dataFields.push(dataField);
      // esvazia o diretório de 12 em 12
      directory = directory.substring(12);
    }

    for (dataField of dataFields) {
      // configura as variáveis para separação dos campos
      const i = dataField.initPos;
      const j = dataField.initPos + dataField.len - 1;
      // localiza o dado em dataValuesGroup, transforma bytes novamente em
      // strings, normaliza o dado e armazena no objeto
      let data = dataValuesGroup.slice(i, j);
      const utf8decoder = new TextDecoder();
      data = utf8decoder.decode(data);
      data = normalizeData(data);
      dataField.data = data;
      // remove propriedades agora desnecessárias
      delete dataField.len;
      delete dataField.initPos;
      // obtém os campos de controle
      if (dataField.tag === '006') {
        controlField006 = dataField.data.replace(/[|#$]/g, ' ');
      }
      if (dataField.tag === '007') {
        controlField007 = dataField.data.replace(/[|#$\r]/g, ' ');
      }
      if (dataField.tag === '008') {
        controlField008 = dataField.data.replace(/[|#$-]/g, ' ');
      }
    }
  }

  function marcTagsRecordHandler(marcTags) {
    // MARC tags handler
    let record = marcTags;
    record
      .replace(/\t\s/, ' ') // LC bib handling (\t+\s)
      .replace(/\t/, ' ') // LC aut handling (\t)
      // eslint-disable-next-line no-irregular-whitespace
      .replace(/    /, ' ') // Pergamum handling (control fields) (non-breaking space)
      // eslint-disable-next-line no-irregular-whitespace
      .replace(/ /, ' '); // Pergamum handling (data fields) (non-breaking space)
    record += '\n';
    const pattern1 = /^FMT/;
    const pattern2 = /^LDR/;
    if (pattern1.test(record) || pattern2.test(record)) {
      // Aleph record handling
      record = record.replace(/\t/g, ' ') // 1
        .replace(/^(FMT\s[A-Z].*?\n)/, '') // 2
        .replace(/LDR\s([0\s-]{4}.*?\n)/, '000 $1') // 3
        .replace(/\n(\d{3})\s(\|.\s)/g, '\n$1    $2') // 4
        .replace(/\n(\d{3})(\d)/g, '\n$1 $2') // 5
        .replace(/\n(\d{3}\s\d\s)/g, '\n$1 '); // 6 (manter essa ordem)
    }
    // configuração das variáveis básicas do registro
    // obtém o líder
    leader = record.match(/^000\s([\d\s-]{4}.*?)\n/);
    if (leader) {
      leader = leader[1];
      leader = leader.replace(/[|#$-]/g, ' ');
    }
    // obtém os campos de controle
    controlField003 = record.match(/\n003\s(.*?)\n/);
    if (controlField003) {
      controlField003 = controlField003[1];
    }
    controlField006 = record.match(/\n006\s([a-z].*?)\n/) || '';
    if (controlField006) {
      controlField006 = controlField006[1];
      controlField006 = controlField006.replace(/[|#$-]/g, ' ');
    }
    controlField007 = record.match(/\n007\s([a-z].*?)\n/) || '';
    if (controlField007) {
      controlField007 = controlField007[1];
      controlField007 = controlField007.replace(/[|#$\r-]/g, ' ');
    }
    controlField008 = record.match(/008\s([\d\s]{5}.*?)\n/) || '';
    if (controlField008) {
      controlField008 = controlField008[1];
      controlField008 = controlField008.replace(/[|#$-]/g, ' ');
    }
    // para cada linha do registro, identifica o campo e seu conteúdo
    // (o conteúdo inclui os indicadores)
    const fields = record.split('\n');
    // seleciona somente os campos desejados (campos 9XX e AAA são descartados)
    const pattern3 = /^([0-8]\d\d)\s([0-9_\s][0-9_\s]\s[$|].*)/;
    const selectedFields = fields.filter((value) => value.match(pattern3));

    for (const field of selectedFields) {
      // para cada campo...
      const match = field.match(pattern3);
      // identifica a tag
      const tagMatch = match[1];
      // e o dado da tag
      let dataMatch = match[2];
      // transforma o dado para o formato igual ao manipulado pelo
      // MARC ISO 2709 record handler
      dataMatch = dataMatch.replace(/\s?\|(.)\s/g, ' $$$1 ');
      // normaliza o dado
      dataMatch = normalizeData(dataMatch);

      // cria o objeto com o dado...
      dataField = {
        tag: tagMatch,
        data: dataMatch,
      };
      // e insere cada objeto dataField na array dataFields
      dataFields.push(dataField);
    }
  }

  // para registros de autoridade da BN, insere "CA-BN ANO"
  function sourceDataFoundBn() {
    if (leader.charAt(6) === 'z' && (controlField003 === 'Br' || controlField003 === 'BR-RjBN')) {
      // cria um objeto com o dado...
      dataField = {
        tag: '670',
        data: `## $a CA-BN ${new Date().getFullYear()}`,
      };
      // e insere o objeto dataField na array dataFields
      dataFields.push(dataField);
    }
  }

  // para registros de autoridade da LC, insere "CA-LC ANO"
  function sourceDataFoundLc() {
    if (leader.charAt(6) === 'z' && (dataField040.match('a DLC'))) {
      // cria um objeto com os dados...
      dataField = {
        tag: '670',
        data: `## $a CA-LC ${new Date().getFullYear()}`,
      };
      // e insere o objeto dataField na array dataFields
      dataFields.push(dataField);
    }
  }

  function unifiedHandler() {
    // eslint-disable-next-line no-shadow
    for (const [index, dataField] of dataFields.entries()) {
      // verifica se a tag é a 040
      if (dataField.tag === '040') {
        // se for, adiciona o subcampo para a agência modificadora do registro
        // e marca como verdadeiro a presença deste campo
        dataFields[index].data = `${dataFields[index].data} $d BR-FlWIK`;
        dataField040 = dataField.data;
      }
      // verifica se a tag é uma das seguintes
      if (dataField.tag === '092' || dataField.tag === '595') {
        // se for, exclua da tabela dataFields
        dataFields.splice(index, 1);
      }
      // TODO: fazer função para deletar campos
    }
    // se não há campo 040, então será criado agora
    if (!dataField040) {
      // cria um objeto com o dado...
      dataField = {
        tag: '040',
        data: '## $a BR-FlWIK $b por $c BR-FlWIK',
      };
      // e insere o objeto dataField na array dataFields
      dataFields.push(dataField);
    }

    // ordena os campos por tags
    dataFields.sort((a, b) => a.tag - b.tag);

    // adiciona a fonte positiva do dado
    sourceDataFoundBn();
    sourceDataFoundLc();

    // eslint-disable-next-line no-shadow
    for (const [index, dataField] of dataFields.entries()) {
      // não pode haver índice 0 na query string para o formulário de edição
      const i = index + 1;
      // se os campos forem maior que 009, então gerarão indicadores
      if (dataField.tag > 9) {
        dataField.ind1 = dataField.data.charAt(0).replace(/[ _]/, '#');
        dataField.ind2 = dataField.data.charAt(1).replace(/[ _]/, '#');
        dataField.data = dataField.data.substring(3);
        dataField.data = dataField.data.normalize('NFC');
        dataField.order = index;
      }
      // se, também, os campos estiverem entre 010 e 830 (com exceção para 856),
      // criará a query string do link para o formulário e a sintaxe da
      // Predefinição Field
      if (parseInt(dataField.tag, 10) === 10
        || (parseInt(dataField.tag, 10) > 12
        && parseInt(dataField.tag, 10) < 831)
        || parseInt(dataField.tag, 10) === 856) {
        // query string, Predefinição Field
        fieldQueryString += `&amp;Field[${i}][tag]=${dataField.tag}`
        + `&amp;Field[${i}][ind1]=${dataField.ind1}`
        + `&amp;Field[${i}][ind2]=${dataField.ind2}`
        + `&amp;Field[${i}][data]=${dataField.data}`;
        // template string, Predefinição Field
        fieldTemplates += '{{Field\n'
        + `|tag=${dataField.tag}\n`
        + `|ind1=${dataField.ind1}\n`
        + `|ind2=${dataField.ind2}\n`
        + `|data=${dataField.data}\n`
        + '}}\n';
      }
    }
  }

  // function to transform a 3rd level object to URL query strings
  const makeUrlParams = (obj, recType) => {
    if (recType === 'Registro bibliográfico') {
      url = new URL(`${window.location.origin}/wiki/Special:FormEdit/BibRecord`);
    } else {
      url = new URL(`${window.location.origin}/wiki/Special:FormEdit/AutRecord`);
    }
    for (const [key, value] of Object.entries(obj)) {
      if (typeof value === 'object') {
        for (const [key2, value2] of Object.entries(value)) {
          if (typeof value2 === 'object') {
            for (const [key3, value3] of Object.entries(value2)) {
              url.searchParams.set(`${key}[${key2}][${key3}]`, value3);
            }
          } else {
            url.searchParams.set(`${key}[${key2}]`, value2);
          }
        }
      } else {
        url.searchParams.set(key, value);
      }
    }
    return url;
  };

  // function to transform a 2nd level object to Template params
  const makeTemplateParams = (obj) => {
    templates = `{{${Object.keys(obj)[0]}\n`;
    for (const [, value] of Object.entries(obj)) {
      if (typeof value === 'object') {
        for (const [key2, value2] of Object.entries(value)) {
          templates += `|${key2}=${value2}\n`;
        }
      }
    }
    templates += `}}\n${fieldTemplates}{{EndOfRecord}}`;
    return templates;
  };

  function setBibVars() {
    // inicializa as variáveis derivadas (registro bibliográfico)
    // líder
    const recordStatus = leader.charAt(5);
    const typeOfRecord = leader.charAt(6);
    const bibliographicLevel = leader.charAt(7);
    let encodingLevel = leader.charAt(17);
    if (encodingLevel === ' ') encodingLevel = '';
    let descriptiveCatalogingForm = leader.charAt(18);
    if (descriptiveCatalogingForm === ' ') descriptiveCatalogingForm = '';
    let multipartResourceRecordLevel = leader.charAt(19);
    if (multipartResourceRecordLevel === ' ') multipartResourceRecordLevel = '';
    // control field 008
    const dateEnteredOnFile = controlField008.substring(0, 6);
    const typeOfDate = controlField008.charAt(6);
    const date1 = controlField008.substring(7, 11);
    const date2 = controlField008.substring(11, 15);
    const placeOfPublication = controlField008.substring(15, 18);
    let illustrations = controlField008.charAt(18);
    if (illustrations === ' ') illustrations = '';
    let targetAudience = controlField008.charAt(22);
    if (targetAudience === ' ') targetAudience = '';
    let formOfItem = controlField008.charAt(23);
    if (formOfItem === ' ') formOfItem = 'r';
    let natureOfContents = controlField008.charAt(24);
    if (natureOfContents === ' ') natureOfContents = '';
    let governmentPublication = controlField008.charAt(28);
    if (governmentPublication === ' ' || governmentPublication === '0') {
      governmentPublication = '';
    }
    let conferencePublication = controlField008.charAt(29);
    if (conferencePublication === '0') conferencePublication = '';
    let festschrift = controlField008.charAt(30);
    if (festschrift === '0') festschrift = '';
    let index = controlField008.charAt(31);
    if (index === '0' || index === ' ') index = '';
    let literaryForm = controlField008.charAt(33);
    if (literaryForm === '0' || literaryForm === ' ') literaryForm = '';
    let biography = controlField008.charAt(34);
    if (biography === ' ') biography = '';
    const language = controlField008.substring(35, 38);
    let modifiedRecord = controlField008.charAt(38);
    if (modifiedRecord === ' ' || modifiedRecord.match('\r')) modifiedRecord = '';
    let catalogingSource = controlField008.charAt(39);
    if (catalogingSource === ' ') catalogingSource = '';

    bibRecordParams = {
      BibRecord: {
        dateEnteredOnFile,
        recordStatus,
        typeOfRecord,
        bibliographicLevel,
        encodingLevel,
        descriptiveCatalogingForm,
        multipartResourceRecordLevel,
        controlField006,
        controlField007,
        typeOfDate,
        date1,
        date2,
        placeOfPublication,
        illustrations,
        targetAudience,
        formOfItem,
        natureOfContents,
        governmentPublication,
        conferencePublication,
        festschrift,
        index,
        literaryForm,
        biography,
        language,
        modifiedRecord,
        catalogingSource,
      },
    };
  }

  function setAutVars() {
    // inicializa as variáveis derivadas (registro de autoridade)
    // líder
    const recordStatus = leader.charAt(5);
    let encodingLevel = leader.charAt(17);
    if (encodingLevel === ' ') encodingLevel = '';
    let punctuationPolicy = leader.charAt(18);
    if (punctuationPolicy === ' ' || punctuationPolicy === '4') punctuationPolicy = '';
    // control field 008
    const dateEnteredOnFile = controlField008.substring(0, 6);
    let directOrIndirectGeogSubdiv = controlField008.charAt(6);
    if (directOrIndirectGeogSubdiv === ' ') directOrIndirectGeogSubdiv = '';
    let romanizationScheme = controlField008.charAt(7);
    if (romanizationScheme === ' ') romanizationScheme = '';
    let languageOfCatalog = controlField008.charAt(8);
    if (languageOfCatalog === ' ') languageOfCatalog = '';
    const kindOfRecord = controlField008.charAt(9);
    const descriptiveCatalogingRules = controlField008.charAt(10);
    const subjectHeadingSystem = controlField008.charAt(11);
    const typeOfSeries = controlField008.charAt(12);
    let numberedOrUnnumberedSeries = controlField008.charAt(13);
    if (numberedOrUnnumberedSeries === ' ') numberedOrUnnumberedSeries = '';
    const headingUseMainOrAddedEntry = controlField008.charAt(14);
    let headingUseSubjectAddedEntry = controlField008.charAt(15);
    if (headingUseSubjectAddedEntry === ' ') headingUseSubjectAddedEntry = '';
    let headingUseSeriesAddedEntry = controlField008.charAt(16);
    if (headingUseSeriesAddedEntry === ' ') headingUseSeriesAddedEntry = '';
    let typeOfSubjectSubdivision = controlField008.charAt(17);
    if (typeOfSubjectSubdivision === ' ') typeOfSubjectSubdivision = '';
    let typeOfGovernmentAgency = controlField008.charAt(28);
    if (typeOfGovernmentAgency === ' ') typeOfGovernmentAgency = '';
    let referenceEvaluation = controlField008.charAt(29);
    if (referenceEvaluation === ' ') referenceEvaluation = '';
    const recordUpdateInProcess = controlField008.charAt(31);
    let undifferentiatedPersonalName = controlField008.charAt(32);
    if (undifferentiatedPersonalName === ' ') undifferentiatedPersonalName = '';
    let levelOfEstablishment = controlField008.charAt(33);
    if (levelOfEstablishment === ' ') levelOfEstablishment = '';
    let modifiedRecord = controlField008.charAt(38);
    if (modifiedRecord === ' ' || modifiedRecord.match('\r')) modifiedRecord = '';
    let catalogingSource = controlField008.charAt(39);
    if (catalogingSource === ' ') catalogingSource = '';

    autRecordParams = {
      AutRecord: {
        dateEnteredOnFile,
        recordStatus,
        encodingLevel,
        punctuationPolicy,
        directOrIndirectGeogSubdiv,
        romanizationScheme,
        languageOfCatalog,
        kindOfRecord,
        descriptiveCatalogingRules,
        subjectHeadingSystem,
        typeOfSeries,
        numberedOrUnnumberedSeries,
        headingUseMainOrAddedEntry,
        headingUseSubjectAddedEntry,
        headingUseSeriesAddedEntry,
        typeOfSubjectSubdivision,
        typeOfGovernmentAgency,
        referenceEvaluation,
        recordUpdateInProcess,
        undifferentiatedPersonalName,
        levelOfEstablishment,
        modifiedRecord,
        catalogingSource,
      },
    };
  }

  // lê o registro, lida com o HTML
    // lê o arquivo de upload
  const input = document.body.querySelector('textarea');
  const uploadedFile = document.querySelector('.upload');
  uploadedFile.addEventListener('change', () => {
    const reader = new FileReader();
    reader.addEventListener('load', () => {
      input.value = reader.result;
    });
    reader.readAsText(uploadedFile.files[0]);
  });

  const readRecordBtn = document.body.querySelector('#readRecord');
  readRecordBtn.addEventListener('click', () => {
    // zera os valores, caso o botão seja clicado mais de uma vez
    dataFields = [];
    fieldQueryString = '';
    fieldTemplates = '';
    const record = input.value;

    if (record) {
      const initialIsoPattern = /^\d\d\d\d/;
      if (initialIsoPattern.test(record)) {
        isoRecordHandler(record);
      } else {
        marcTagsRecordHandler(record);
      }
      unifiedHandler();

      // define se é "Registro bibliográfico" ou se é "Registro de autoridade"
      const recordType = document.body.querySelector('input:checked').value;
      if (recordType === 'Registro bibliográfico') {
        setBibVars();
        url = makeUrlParams(bibRecordParams, recordType);
        templates = makeTemplateParams(bibRecordParams);
      } else {
        setAutVars();
        url = makeUrlParams(autRecordParams, recordType);
        templates = makeTemplateParams(autRecordParams);
      }

      // cria o link para a importação do registro
      url.search += `${fieldQueryString}`;
      const importClass = document.body.querySelector('.importLink');
      importClass.innerHTML = '';
      const createRecordLink = document.createElement('a');
      createRecordLink.setAttribute('href', url.href);
      createRecordLink.setAttribute('target', '_blank');
      createRecordLink.innerHTML = '&gt; &lt;b&gt;Importar registro&lt;/b&gt;';
      importClass.appendChild(createRecordLink);

      // cria div para pré-visualização dos dados do registro
      const templatePreview = document.body.querySelector('.templatePreview');
      templatePreview.innerHTML = '';
      const h2 = document.createElement('h2');
      h2.innerText = 'Pré-visualização do registro';
      templatePreview.appendChild(h2);
      const preTag = document.createElement('pre');
      preTag.innerText = templates;
      templatePreview.appendChild(preTag);
    } else {
      alert('É necessário colar um registro MARC na caixa de texto');
    }
  });
}

if (document.readyState === 'complete' || (document.readyState !== 'loading')) {
  mainFunc();
} else {
  document.addEventListener('DOMContentLoaded', mainFunc);
}