156 lines
3.7 KiB
JavaScript
156 lines
3.7 KiB
JavaScript
import {Base} from './Base.js';
|
|
import {Number as NumberT} from './Number.js';
|
|
import * as utils from './utils.js';
|
|
|
|
class StringT extends Base {
|
|
constructor(length, encoding = 'ascii') {
|
|
super();
|
|
this.length = length;
|
|
this.encoding = encoding;
|
|
}
|
|
|
|
decode(stream, parent) {
|
|
let length, pos;
|
|
|
|
let { encoding } = this;
|
|
if (typeof encoding === 'function') {
|
|
encoding = encoding.call(parent, parent) || 'ascii';
|
|
}
|
|
let width = encodingWidth(encoding);
|
|
|
|
if (this.length != null) {
|
|
length = utils.resolveLength(this.length, stream, parent);
|
|
} else {
|
|
let buffer;
|
|
({buffer, length, pos} = stream);
|
|
|
|
while ((pos < length - width + 1) &&
|
|
(buffer[pos] !== 0x00 ||
|
|
(width === 2 && buffer[pos+1] !== 0x00)
|
|
)) {
|
|
pos += width;
|
|
}
|
|
|
|
length = pos - stream.pos;
|
|
}
|
|
|
|
|
|
const string = stream.readString(length, encoding);
|
|
|
|
if ((this.length == null) && (stream.pos < stream.length)) {
|
|
stream.pos+=width;
|
|
}
|
|
|
|
return string;
|
|
}
|
|
|
|
size(val, parent) {
|
|
// Use the defined value if no value was given
|
|
if (val === undefined || val === null) {
|
|
return utils.resolveLength(this.length, null, parent);
|
|
}
|
|
|
|
let { encoding } = this;
|
|
if (typeof encoding === 'function') {
|
|
encoding = encoding.call(parent != null ? parent.val : undefined, parent != null ? parent.val : undefined) || 'ascii';
|
|
}
|
|
|
|
if (encoding === 'utf16be') {
|
|
encoding = 'utf16le';
|
|
}
|
|
|
|
let size = byteLength(val, encoding);
|
|
if (this.length instanceof NumberT) {
|
|
size += this.length.size();
|
|
}
|
|
|
|
if ((this.length == null)) {
|
|
size += encodingWidth(encoding);
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
encode(stream, val, parent) {
|
|
let { encoding } = this;
|
|
if (typeof encoding === 'function') {
|
|
encoding = encoding.call(parent != null ? parent.val : undefined, parent != null ? parent.val : undefined) || 'ascii';
|
|
}
|
|
|
|
if (this.length instanceof NumberT) {
|
|
this.length.encode(stream, byteLength(val, encoding));
|
|
}
|
|
|
|
stream.writeString(val, encoding);
|
|
|
|
if ((this.length == null)) {
|
|
return encodingWidth(encoding) == 2 ?
|
|
stream.writeUInt16LE(0x0000) :
|
|
stream.writeUInt8(0x00);
|
|
}
|
|
}
|
|
}
|
|
|
|
function encodingWidth(encoding) {
|
|
switch(encoding) {
|
|
case 'ascii':
|
|
case 'utf8': // utf8 is a byte-based encoding for zero-term string
|
|
return 1;
|
|
case 'utf16le':
|
|
case 'utf16-le':
|
|
case 'utf-16be':
|
|
case 'utf-16le':
|
|
case 'utf16be':
|
|
case 'utf16-be':
|
|
case 'ucs2':
|
|
return 2;
|
|
default:
|
|
//TODO: assume all other encodings are 1-byters
|
|
//throw new Error('Unknown encoding ' + encoding);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
function byteLength(string, encoding) {
|
|
switch (encoding) {
|
|
case 'ascii':
|
|
return string.length;
|
|
case 'utf8':
|
|
let len = 0;
|
|
for (let i = 0; i < string.length; i++) {
|
|
let c = string.charCodeAt(i);
|
|
|
|
if (c >= 0xd800 && c <= 0xdbff && i < string.length - 1) {
|
|
let c2 = string.charCodeAt(++i);
|
|
if ((c2 & 0xfc00) === 0xdc00) {
|
|
c = ((c & 0x3ff) << 10) + (c2 & 0x3ff) + 0x10000;
|
|
} else {
|
|
// unmatched surrogate.
|
|
i--;
|
|
}
|
|
}
|
|
|
|
if ((c & 0xffffff80) === 0) {
|
|
len++;
|
|
} else if ((c & 0xfffff800) === 0) {
|
|
len += 2;
|
|
} else if ((c & 0xffff0000) === 0) {
|
|
len += 3;
|
|
} else if ((c & 0xffe00000) === 0) {
|
|
len += 4;
|
|
}
|
|
}
|
|
return len;
|
|
case 'utf16le':
|
|
case 'utf16-le':
|
|
case 'utf16be':
|
|
case 'utf16-be':
|
|
case 'ucs2':
|
|
return string.length * 2;
|
|
default:
|
|
throw new Error('Unknown encoding ' + encoding);
|
|
}
|
|
}
|
|
|
|
export {StringT as String};
|