NatLibFi/marc-record-converters

View on GitHub
lib/converters/marc21slim-xml.js

Summary

Maintainability
D
2 days
Test Coverage
/**
 *
 * @licstart  The following is the entire license notice for the JavaScript code in this file. 
 *
 * ISC License
 *  
 * Modified work: Copyright (c) 2015, 2017 University Of Helsinki (The National Library Of Finland)  
 * Original work: Copyright (c) 2015 Pasi Tuominen
 *  
 * Permission to use, copy, modify, and/or distribute this software for
 * any purpose with or without fee is hereby granted, provided that the
 * above copyright notice and this permission notice appear in all
 * copies.
 *  
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY
 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *  
 * @licend  The above is the entire license notice
 * for the JavaScript code in this file.
 *
 **/

/* istanbul ignore next: umd wrapper */
(function(root, factory) {

  'use strict';

  if (typeof define === 'function' && define.amd) {
    define(['marc-record-js', 'xmldom-xplat'], factory);
  } else if (typeof module === 'object' && module.exports) {
    module.exports = factory(require('marc-record-js'), require('xmldom-xplat'));
  }

}(this, factory));

function factory(MarcRecord, xmldom)
{

  'use strict';

  var MARC21SLIM_NAMESPACE = 'http://www.loc.gov/MARC21/slim',
      NODE_TYPE = {
        text_node: 3
      };

  function convertToXML(record_data)
  {

    function mkDatafield(field) {
      
      var datafield = mkElement('datafield');

      datafield.setAttribute('tag', field.tag);
      datafield.setAttribute('ind1', formatIndicator(field.ind1));
      datafield.setAttribute('ind2', formatIndicator(field.ind2));

      field.subfields.forEach(function(subfield) {

        var sub = mkElementValue('subfield', subfield.value);

        sub.setAttribute('code', subfield.code);
        datafield.appendChild(sub);

      });

      return datafield;

    }

    function formatIndicator(ind) {
      return ind == '_' ? ' ' : ind;
    }

    function mkElementValue(name, value) {

      var el = mkElement(name),
          t = xmldoc.createTextNode(value);

      el.appendChild(t);

      return el;

    }

    function mkElement(name) {
      return xmldoc.createElement(name);
    }

    function mkControlfield(tag, value) {

      var cf = mkElement('controlfield'),
          t = xmldoc.createTextNode(value);

      cf.setAttribute('tag', tag);
      cf.appendChild(t);

      return cf;

    }
    
    function convertRecord(record)
    {
      
      var xml = mkElement('record'),
          leader = mkElementValue('leader', record.leader);

      xml.setAttribute('xmlns', MARC21SLIM_NAMESPACE);
      xml.appendChild(leader);
      
      record.getControlfields().forEach(function(field) {
        xml.appendChild(mkControlfield(field.tag, field.value));
      });
      
      record.getDatafields().forEach(function(field) {
        xml.appendChild(mkDatafield(field));
      });

      return xml;
      
    }

    var xmldoc = new xmldom.DOMImplementation().createDocument(),
        doc = Array.isArray(record_data) ? record_data.map(convertRecord).reduce(function(product, item) {

          product.appendChild(item);
          return product;
          
        }, xmldoc.createElement('collection'))
        : convertRecord(record_data);
    
    return new xmldom.XMLSerializer().serializeToString(doc);

  }

  function convertFromXML(str)
  {

    function getRecords(offset, records)
    {

      function notTextNode(node) {
        return node.nodeType !== NODE_TYPE.text_node;
      }

      function handleLeaderNode(node) {
        if (node.childNodes[0] !== undefined && node.childNodes[0].nodeType === NODE_TYPE.text_node && node.childNodes[0].data.trim().length > 0) {
          record.leader = node.childNodes[0].data;
        } else {
          throw new Error('Record has invalid leader');
        }
      }

      function handleControlfieldNode(node) {
        
        var value,
            tag = node.getAttribute('tag');

        if (node.childNodes[0] !== undefined && node.childNodes[0].nodeType === NODE_TYPE.text_node) {
          value = node.childNodes[0].data;
          record.appendControlField([tag, value]);
        } else {
          throw new Error('Unable to parse controlfield: ' + tag);
        }

      }

      function handleDatafieldNode(node) {

        var tag = node.getAttribute('tag'),
            ind1 = node.getAttribute('ind1'),
            ind2 = node.getAttribute('ind2'),
            subfields  = Array.prototype.slice.call(node.childNodes).filter(notTextNode).map(function(subfieldNode) {
              
              var code = subfieldNode.getAttribute('code');
              var text = getChildTextNodeContents(subfieldNode).join('');

              return {
                code: code,
                value: text
              };

            });

        record.appendField({
          tag: tag,
          ind1: ind1,
          ind2: ind2,
          subfields: subfields
        });

      }
      
      function getChildTextNodeContents(node) {
        
        var nodes_child = Array.prototype.slice.call(node.childNodes),
            nodes_text = nodes_child.filter(function(node) {
              return node.nodeType === NODE_TYPE.text_node;
            });

        return nodes_text.map(function(node) {
          return node.data;
        });

      }

      var nodes_child, node_record,
          record = new MarcRecord();

      offset = offset === undefined ? 0 : offset;
      records = records === undefined ? [] : records;     

      node_record = nodes_record.item(offset);
      
      if (node_record === undefined) {
        return records;
      } else {

        nodes_child = (node_record !== undefined) ?  Array.prototype.slice.call(node_record.childNodes) : [];

        nodes_child.filter(notTextNode).forEach(function(node) {
          switch (node.localName) {
          case 'leader':
            handleLeaderNode(node);
            break;
          case 'controlfield':
            handleControlfieldNode(node);
            break;
          case 'datafield':
            handleDatafieldNode(node);
            break;
          case 'recordTypeType':
          case 'id':
            break;
          default:
            throw new Error('Unable to parse node: ' + node.localName);
          }
        });

        return getRecords(offset + 1, records.concat(record));

      }

    }
    
    var nodes_record,
        xmldoc = new xmldom.DOMImplementation().createDocument(),
        xml = new xmldom.DOMParser().parseFromString(str);
    
    try {
      nodes_record = xml.getElementsByTagNameNS(MARC21SLIM_NAMESPACE, 'record');
      nodes_record = nodes_record.length === 0 ? xml.getElementsByTagName('record') : nodes_record;
    } catch (e) {
      throw new Error('Invalid XML object');
    }

    if (nodes_record.length === 0) {
      throw new Error('No records found in XML');
    } else {
      return getRecords().reduce(function(product, item) {
        return Array.isArray(product) ? product.concat(item) : [product, item];
      });
    }

  }

  return {
    from: convertFromXML,
    to: convertToXML
  };

}