marc8
Version:
A Node.js module for converting MARC8 encoding to Unicode
155 lines (135 loc) • 4.76 kB
JavaScript
;
var CODESETS = require('./marc8_mapping').CODESETS;
var unorm = require('unorm');
var translate = function(marc8_string, options) {
if (!marc8_string) return '';
var buffer;
if (typeof marc8_string === 'string' || marc8_string instanceof String) {
buffer = new Buffer(marc8_string, 'binary');
} else {
buffer = marc8_string;
}
var basic_latin = 0x42;
var ansel = 0x45;
var G0_SET = [0x28, 0x2c, 0x24]; // ['(', ',', '$'];
var G1_SET = [0x29, 0x2d, 0x24]; // [')', '-', '$'];
options = options || {};
var normalization = options.normalization;
if (typeof normalization === 'undefiend') {
normalization = 'NFC';
}
var invalidReplacement = options.replace;
if (typeof invalidReplacement === 'undefined') {
invalidReplacement = "\uFFFD";
}
var expandNCR = options.expandNCR;
if (typeof expandNCR === 'undefined') {
expandNCR = true;
}
var g0 = options.G0 || basic_latin; // basic_latin
var g1 = options.G1 || ansel; // ansel
var buffer = new Buffer(marc8_string, 'binary');
var uniList = [];
var combinings = [];
var pos = 0;
var charset, nextByte, codePoint, codeSet, uniString;
while (pos < buffer.length) {
if (buffer[pos] === 0x1b) {
nextByte = buffer[pos+1]
if (G0_SET.indexOf(nextByte) >=0) {
if (buffer.length >= pos+3) {
if (buffer[pos+2] == 0x2c /*,*/ && nextByte == 0x24 /*'$'*/) {
pos += 1;
}
g0 = buffer[pos+2];
pos = pos + 3;
continue;
} else {
uniList.append(String.fromCharCode(buffer[pos]));
pos += 1;
continue;
}
} else if (G1_SET.indexOf(nextByte) >= 0) {
if (buffer[pos+2] == 0x2d /*'-'*/ && nextByte == 0x24 /*'$'*/) {
pos += 1;
}
g1 = buffer[pos+2];
pos += 3;
continue;
} else {
charset = nextByte;
if (charset in CODESETS) {
g0 = charset;
pos += 2;
} else if (charset === 0x73) {
g0 = basic_latin;
pos += 2;
if (pos === buffer.length) {
break;
}
}
}
}
var isMultibyte = (g0 === 0x31);
if (isMultibyte) {
codePoint = buffer[pos]*65536 + buffer[pos+1]*256 + buffer[pos+2];
pos += 3;
} else {
codePoint = buffer[pos]
pos += 1;
}
if (codePoint < 0x20 || (codePoint > 0x80 && codePoint < 0xa0)) {
uni = String.fromCharCode(codePoint);
continue;
}
codeSet = (codePoint > 0x80 && !isMultibyte) ? g1 : g0;
if (codeSet in CODESETS && codePoint in CODESETS[codeSet]) {
var pair = CODESETS[codeSet][codePoint];
var uni = pair[0];
var cflag = pair[1];
if (cflag == 1) {
combinings.push(String.fromCharCode(uni));
} else {
uniList.push(String.fromCharCode(uni));
if (combinings.length > 0) {
uniList = uniList.concat(combinings);
combinings = [];
}
}
} else {
if (options.invalid === 'replace') {
if (uniList[uniList.length-1] !== invalidReplacement) {
uniList.push(invalidReplacement);
pos += 1;
}
} else {
throw "MARC8, input byte offset " + pos + ", code set: 0x" + codeSet.toString(16) +
", code point: 0x" + codePoint.toString(16);
}
}
}
uniString = uniList.join('');
if (expandNCR) {
uniString = uniString.replace(/&#x([0-9A-F]{4,6});/gi, function(_, x) {
return String.fromCharCode(parseInt("0x"+x));
});
}
if (normalization) {
switch (normalization.toUpperCase()) {
case 'NFC':
uniString = unorm.nfc(uniString);
break;
case 'NFD':
uniString = unorm.nfd(uniString);
break;
case 'NFKC':
uniString = unorm.nfkc(uniString);
break;
case 'NFKD':
uniString = unorm.nfkd(uniString);
break;
}
}
return uniString;
};
module.exports = translate;