|
// |
|
// jDataView by Vjeux - Jan 2010 |
|
// |
|
// A unique way to read a binary file in the browser |
|
// http://github.com/vjeux/jDataView |
|
// http://blog.vjeux.com/ <[email protected]> |
|
// |
|
|
|
(function (global) { |
|
|
|
var compatibility = { |
|
ArrayBuffer: typeof ArrayBuffer !== 'undefined', |
|
DataView: typeof DataView !== 'undefined' && |
|
('getFloat64' in DataView.prototype || // Chrome |
|
'getFloat64' in new DataView(new ArrayBuffer(1))), // Node |
|
// NodeJS Buffer in v0.5.5 and newer |
|
NodeBuffer: typeof Buffer !== 'undefined' && 'readInt16LE' in Buffer.prototype |
|
}; |
|
|
|
var dataTypes = { |
|
'Int8': 1, |
|
'Int16': 2, |
|
'Int32': 4, |
|
'Uint8': 1, |
|
'Uint16': 2, |
|
'Uint32': 4, |
|
'Float32': 4, |
|
'Float64': 8 |
|
}; |
|
|
|
var nodeNaming = { |
|
'Int8': 'Int8', |
|
'Int16': 'Int16', |
|
'Int32': 'Int32', |
|
'Uint8': 'UInt8', |
|
'Uint16': 'UInt16', |
|
'Uint32': 'UInt32', |
|
'Float32': 'Float', |
|
'Float64': 'Double' |
|
}; |
|
|
|
var jDataView = function (buffer, byteOffset, byteLength, littleEndian) { |
|
if (!(this instanceof jDataView)) { |
|
throw new Error("jDataView constructor may not be called as a function"); |
|
} |
|
|
|
this.buffer = buffer; |
|
|
|
// Handle Type Errors |
|
if (!(compatibility.NodeBuffer && buffer instanceof Buffer) && |
|
!(compatibility.ArrayBuffer && buffer instanceof ArrayBuffer) && |
|
typeof buffer !== 'string') { |
|
throw new TypeError('jDataView buffer has an incompatible type'); |
|
} |
|
|
|
// Check parameters and existing functionnalities |
|
this._isArrayBuffer = compatibility.ArrayBuffer && buffer instanceof ArrayBuffer; |
|
this._isDataView = compatibility.DataView && this._isArrayBuffer; |
|
this._isNodeBuffer = compatibility.NodeBuffer && buffer instanceof Buffer; |
|
|
|
// Default Values |
|
this._littleEndian = littleEndian === undefined ? false : littleEndian; |
|
|
|
var bufferLength = this._isArrayBuffer ? buffer.byteLength : buffer.length; |
|
if (byteOffset === undefined) { |
|
byteOffset = 0; |
|
} |
|
this.byteOffset = byteOffset; |
|
|
|
if (byteLength === undefined) { |
|
byteLength = bufferLength - byteOffset; |
|
} |
|
this.byteLength = byteLength; |
|
|
|
if (!this._isDataView) { |
|
// Do additional checks to simulate DataView |
|
if (typeof byteOffset !== 'number') { |
|
throw new TypeError('jDataView byteOffset is not a number'); |
|
} |
|
if (typeof byteLength !== 'number') { |
|
throw new TypeError('jDataView byteLength is not a number'); |
|
} |
|
if (byteOffset < 0) { |
|
throw new Error('jDataView byteOffset is negative'); |
|
} |
|
if (byteLength < 0) { |
|
throw new Error('jDataView byteLength is negative'); |
|
} |
|
} |
|
|
|
// Instanciate |
|
if (this._isDataView) { |
|
this._view = new DataView(buffer, byteOffset, byteLength); |
|
this._start = 0; |
|
} |
|
this._start = byteOffset; |
|
if (byteOffset + byteLength > bufferLength) { |
|
throw new Error("jDataView (byteOffset + byteLength) value is out of bounds"); |
|
} |
|
|
|
this._offset = 0; |
|
|
|
// Create uniform reading methods (wrappers) for the following data types |
|
|
|
if (this._isDataView) { // DataView: we use the direct method |
|
for (var type in dataTypes) { |
|
if (!dataTypes.hasOwnProperty(type)) { |
|
continue; |
|
} |
|
(function(type, view){ |
|
var size = dataTypes[type]; |
|
view['get' + type] = function (byteOffset, littleEndian) { |
|
// Handle the lack of endianness |
|
if (littleEndian === undefined) { |
|
littleEndian = view._littleEndian; |
|
} |
|
|
|
// Handle the lack of byteOffset |
|
if (byteOffset === undefined) { |
|
byteOffset = view._offset; |
|
} |
|
|
|
// Move the internal offset forward |
|
view._offset = byteOffset + size; |
|
|
|
return view._view['get' + type](byteOffset, littleEndian); |
|
} |
|
})(type, this); |
|
} |
|
} else if (this._isNodeBuffer && compatibility.NodeBuffer) { |
|
for (var type in dataTypes) { |
|
if (!dataTypes.hasOwnProperty(type)) { |
|
continue; |
|
} |
|
|
|
var name; |
|
if (type === 'Int8' || type === 'Uint8') { |
|
name = 'read' + nodeNaming[type]; |
|
} else if (littleEndian) { |
|
name = 'read' + nodeNaming[type] + 'LE'; |
|
} else { |
|
name = 'read' + nodeNaming[type] + 'BE'; |
|
} |
|
|
|
(function(type, view, name){ |
|
var size = dataTypes[type]; |
|
view['get' + type] = function (byteOffset, littleEndian) { |
|
// Handle the lack of endianness |
|
if (littleEndian === undefined) { |
|
littleEndian = view._littleEndian; |
|
} |
|
|
|
// Handle the lack of byteOffset |
|
if (byteOffset === undefined) { |
|
byteOffset = view._offset; |
|
} |
|
|
|
// Move the internal offset forward |
|
view._offset = byteOffset + size; |
|
|
|
return view.buffer[name](view._start + byteOffset); |
|
} |
|
})(type, this, name); |
|
} |
|
} else { |
|
for (var type in dataTypes) { |
|
if (!dataTypes.hasOwnProperty(type)) { |
|
continue; |
|
} |
|
(function(type, view){ |
|
var size = dataTypes[type]; |
|
view['get' + type] = function (byteOffset, littleEndian) { |
|
// Handle the lack of endianness |
|
if (littleEndian === undefined) { |
|
littleEndian = view._littleEndian; |
|
} |
|
|
|
// Handle the lack of byteOffset |
|
if (byteOffset === undefined) { |
|
byteOffset = view._offset; |
|
} |
|
|
|
// Move the internal offset forward |
|
view._offset = byteOffset + size; |
|
|
|
if (view._isArrayBuffer && (view._start + byteOffset) % size === 0 && (size === 1 || littleEndian)) { |
|
// ArrayBuffer: we use a typed array of size 1 if the alignment is good |
|
// ArrayBuffer does not support endianess flag (for size > 1) |
|
return new global[type + 'Array'](view.buffer, view._start + byteOffset, 1)[0]; |
|
} else { |
|
// Error checking: |
|
if (typeof byteOffset !== 'number') { |
|
throw new TypeError('jDataView byteOffset is not a number'); |
|
} |
|
if (byteOffset + size > view.byteLength) { |
|
throw new Error('jDataView (byteOffset + size) value is out of bounds'); |
|
} |
|
|
|
return view['_get' + type](view._start + byteOffset, littleEndian); |
|
} |
|
} |
|
})(type, this); |
|
} |
|
} |
|
}; |
|
|
|
if (compatibility.NodeBuffer) { |
|
jDataView.createBuffer = function () { |
|
var buffer = new Buffer(arguments.length); |
|
for (var i = 0; i < arguments.length; ++i) { |
|
buffer[i] = arguments[i]; |
|
} |
|
return buffer; |
|
} |
|
} else if (compatibility.ArrayBuffer) { |
|
jDataView.createBuffer = function () { |
|
var buffer = new ArrayBuffer(arguments.length); |
|
var view = new Int8Array(buffer); |
|
for (var i = 0; i < arguments.length; ++i) { |
|
view[i] = arguments[i]; |
|
} |
|
return buffer; |
|
} |
|
} else { |
|
jDataView.createBuffer = function () { |
|
return String.fromCharCode.apply(null, arguments); |
|
} |
|
} |
|
|
|
jDataView.prototype = { |
|
compatibility: compatibility, |
|
|
|
// Helpers |
|
|
|
getString: function (length, byteOffset) { |
|
var value; |
|
|
|
// Handle the lack of byteOffset |
|
if (byteOffset === undefined) { |
|
byteOffset = this._offset; |
|
} |
|
|
|
// Error Checking |
|
if (typeof byteOffset !== 'number') { |
|
throw new TypeError('jDataView byteOffset is not a number'); |
|
} |
|
if (length < 0 || byteOffset + length > this.byteLength) { |
|
throw new Error('jDataView length or (byteOffset+length) value is out of bounds'); |
|
} |
|
|
|
if (this._isNodeBuffer) { |
|
value = this.buffer.toString('ascii', this._start + byteOffset, this._start + byteOffset + length); |
|
} |
|
else { |
|
value = ''; |
|
for (var i = 0; i < length; ++i) { |
|
var char = this.getUint8(byteOffset + i); |
|
value += String.fromCharCode(char > 127 ? 65533 : char); |
|
} |
|
} |
|
|
|
this._offset = byteOffset + length; |
|
return value; |
|
}, |
|
|
|
getChar: function (byteOffset) { |
|
return this.getString(1, byteOffset); |
|
}, |
|
|
|
tell: function () { |
|
return this._offset; |
|
}, |
|
|
|
seek: function (byteOffset) { |
|
if (typeof byteOffset !== 'number') { |
|
throw new TypeError('jDataView byteOffset is not a number'); |
|
} |
|
if (byteOffset < 0 || byteOffset > this.byteLength) { |
|
throw new Error('jDataView byteOffset value is out of bounds'); |
|
} |
|
|
|
return this._offset = byteOffset; |
|
}, |
|
|
|
// Compatibility functions on a String Buffer |
|
|
|
_endianness: function (byteOffset, pos, max, littleEndian) { |
|
return byteOffset + (littleEndian ? max - pos - 1 : pos); |
|
}, |
|
|
|
_getFloat64: function (byteOffset, littleEndian) { |
|
var b0 = this._getUint8(this._endianness(byteOffset, 0, 8, littleEndian)), |
|
b1 = this._getUint8(this._endianness(byteOffset, 1, 8, littleEndian)), |
|
b2 = this._getUint8(this._endianness(byteOffset, 2, 8, littleEndian)), |
|
b3 = this._getUint8(this._endianness(byteOffset, 3, 8, littleEndian)), |
|
b4 = this._getUint8(this._endianness(byteOffset, 4, 8, littleEndian)), |
|
b5 = this._getUint8(this._endianness(byteOffset, 5, 8, littleEndian)), |
|
b6 = this._getUint8(this._endianness(byteOffset, 6, 8, littleEndian)), |
|
b7 = this._getUint8(this._endianness(byteOffset, 7, 8, littleEndian)), |
|
|
|
sign = 1 - (2 * (b0 >> 7)), |
|
exponent = ((((b0 << 1) & 0xff) << 3) | (b1 >> 4)) - (Math.pow(2, 10) - 1), |
|
|
|
// Binary operators such as | and << operate on 32 bit values, using + and Math.pow(2) instead |
|
mantissa = ((b1 & 0x0f) * Math.pow(2, 48)) + (b2 * Math.pow(2, 40)) + (b3 * Math.pow(2, 32)) + |
|
(b4 * Math.pow(2, 24)) + (b5 * Math.pow(2, 16)) + (b6 * Math.pow(2, 8)) + b7; |
|
|
|
if (exponent === 1024) { |
|
if (mantissa !== 0) { |
|
return NaN; |
|
} else { |
|
return sign * Infinity; |
|
} |
|
} |
|
|
|
if (exponent === -1023) { // Denormalized |
|
return sign * mantissa * Math.pow(2, -1022 - 52); |
|
} |
|
|
|
return sign * (1 + mantissa * Math.pow(2, -52)) * Math.pow(2, exponent); |
|
}, |
|
|
|
_getFloat32: function (byteOffset, littleEndian) { |
|
var b0 = this._getUint8(this._endianness(byteOffset, 0, 4, littleEndian)), |
|
b1 = this._getUint8(this._endianness(byteOffset, 1, 4, littleEndian)), |
|
b2 = this._getUint8(this._endianness(byteOffset, 2, 4, littleEndian)), |
|
b3 = this._getUint8(this._endianness(byteOffset, 3, 4, littleEndian)), |
|
|
|
sign = 1 - (2 * (b0 >> 7)), |
|
exponent = (((b0 << 1) & 0xff) | (b1 >> 7)) - 127, |
|
mantissa = ((b1 & 0x7f) << 16) | (b2 << 8) | b3; |
|
|
|
if (exponent === 128) { |
|
if (mantissa !== 0) { |
|
return NaN; |
|
} else { |
|
return sign * Infinity; |
|
} |
|
} |
|
|
|
if (exponent === -127) { // Denormalized |
|
return sign * mantissa * Math.pow(2, -126 - 23); |
|
} |
|
|
|
return sign * (1 + mantissa * Math.pow(2, -23)) * Math.pow(2, exponent); |
|
}, |
|
|
|
_getInt32: function (byteOffset, littleEndian) { |
|
var b = this._getUint32(byteOffset, littleEndian); |
|
return b > Math.pow(2, 31) - 1 ? b - Math.pow(2, 32) : b; |
|
}, |
|
|
|
_getUint32: function (byteOffset, littleEndian) { |
|
var b3 = this._getUint8(this._endianness(byteOffset, 0, 4, littleEndian)), |
|
b2 = this._getUint8(this._endianness(byteOffset, 1, 4, littleEndian)), |
|
b1 = this._getUint8(this._endianness(byteOffset, 2, 4, littleEndian)), |
|
b0 = this._getUint8(this._endianness(byteOffset, 3, 4, littleEndian)); |
|
|
|
return (b3 * Math.pow(2, 24)) + (b2 << 16) + (b1 << 8) + b0; |
|
}, |
|
|
|
_getInt16: function (byteOffset, littleEndian) { |
|
var b = this._getUint16(byteOffset, littleEndian); |
|
return b > Math.pow(2, 15) - 1 ? b - Math.pow(2, 16) : b; |
|
}, |
|
|
|
_getUint16: function (byteOffset, littleEndian) { |
|
var b1 = this._getUint8(this._endianness(byteOffset, 0, 2, littleEndian)), |
|
b0 = this._getUint8(this._endianness(byteOffset, 1, 2, littleEndian)); |
|
|
|
return (b1 << 8) + b0; |
|
}, |
|
|
|
_getInt8: function (byteOffset) { |
|
var b = this._getUint8(byteOffset); |
|
return b > Math.pow(2, 7) - 1 ? b - Math.pow(2, 8) : b; |
|
}, |
|
|
|
_getUint8: function (byteOffset) { |
|
if (this._isArrayBuffer) { |
|
return new Uint8Array(this.buffer, byteOffset, 1)[0]; |
|
} |
|
else if (this._isNodeBuffer) { |
|
return this.buffer[byteOffset]; |
|
} else { |
|
return this.buffer.charCodeAt(byteOffset) & 0xff; |
|
} |
|
} |
|
}; |
|
|
|
if (typeof jQuery !== 'undefined' && jQuery.fn.jquery >= "1.6.2") { |
|
var convertResponseBodyToText = function (byteArray) { |
|
// http://jsperf.com/vbscript-binary-download/6 |
|
var scrambledStr; |
|
try { |
|
scrambledStr = IEBinaryToArray_ByteStr(byteArray); |
|
} catch (e) { |
|
// http://stackoverflow.com/questions/1919972/how-do-i-access-xhr-responsebody-for-binary-data-from-javascript-in-ie |
|
// http://miskun.com/javascript/internet-explorer-and-binary-files-data-access/ |
|
var IEBinaryToArray_ByteStr_Script = |
|
"Function IEBinaryToArray_ByteStr(Binary)\r\n"+ |
|
" IEBinaryToArray_ByteStr = CStr(Binary)\r\n"+ |
|
"End Function\r\n"+ |
|
"Function IEBinaryToArray_ByteStr_Last(Binary)\r\n"+ |
|
" Dim lastIndex\r\n"+ |
|
" lastIndex = LenB(Binary)\r\n"+ |
|
" if lastIndex mod 2 Then\r\n"+ |
|
" IEBinaryToArray_ByteStr_Last = AscB( MidB( Binary, lastIndex, 1 ) )\r\n"+ |
|
" Else\r\n"+ |
|
" IEBinaryToArray_ByteStr_Last = -1\r\n"+ |
|
" End If\r\n"+ |
|
"End Function\r\n"; |
|
|
|
// http://msdn.microsoft.com/en-us/library/ms536420(v=vs.85).aspx |
|
// proprietary IE function |
|
window.execScript(IEBinaryToArray_ByteStr_Script, 'vbscript'); |
|
|
|
scrambledStr = IEBinaryToArray_ByteStr(byteArray); |
|
} |
|
|
|
var lastChr = IEBinaryToArray_ByteStr_Last(byteArray), |
|
result = "", |
|
i = 0, |
|
l = scrambledStr.length % 8, |
|
thischar; |
|
while (i < l) { |
|
thischar = scrambledStr.charCodeAt(i++); |
|
result += String.fromCharCode(thischar & 0xff, thischar >> 8); |
|
} |
|
l = scrambledStr.length |
|
while (i < l) { |
|
result += String.fromCharCode( |
|
(thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8, |
|
(thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8, |
|
(thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8, |
|
(thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8, |
|
(thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8, |
|
(thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8, |
|
(thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8, |
|
(thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8); |
|
} |
|
if (lastChr > -1) { |
|
result += String.fromCharCode(lastChr); |
|
} |
|
return result; |
|
}; |
|
|
|
jQuery.ajaxSetup({ |
|
converters: { |
|
'* dataview': function(data) { |
|
return new jDataView(data); |
|
} |
|
}, |
|
accepts: { |
|
dataview: "text/plain; charset=x-user-defined" |
|
}, |
|
responseHandler: { |
|
dataview: function (responses, options, xhr) { |
|
// Array Buffer Firefox |
|
if ('mozResponseArrayBuffer' in xhr) { |
|
responses.text = xhr.mozResponseArrayBuffer; |
|
} |
|
// Array Buffer Chrome |
|
else if ('responseType' in xhr && xhr.responseType === 'arraybuffer' && xhr.response) { |
|
responses.text = xhr.response; |
|
} |
|
// Internet Explorer (Byte array accessible through VBScript -- convert to text) |
|
else if ('responseBody' in xhr) { |
|
responses.text = convertResponseBodyToText(xhr.responseBody); |
|
} |
|
// Older Browsers |
|
else { |
|
responses.text = xhr.responseText; |
|
} |
|
} |
|
} |
|
}); |
|
|
|
jQuery.ajaxPrefilter('dataview', function(options, originalOptions, jqXHR) { |
|
// trying to set the responseType on IE 6 causes an error |
|
if (jQuery.support.ajaxResponseType) { |
|
if (!options.hasOwnProperty('xhrFields')) { |
|
options.xhrFields = {}; |
|
} |
|
options.xhrFields.responseType = 'arraybuffer'; |
|
} |
|
options.mimeType = 'text/plain; charset=x-user-defined'; |
|
}); |
|
} |
|
|
|
global.jDataView = (global.module || {}).exports = jDataView; |
|
if (typeof module !== 'undefined') { |
|
module.exports = jDataView; |
|
} |
|
|
|
})(this); |