"use strict";
/**
* @fileoverview Binary, ByteArray and ByteString classes as defined in
* [CommonJS Binary/B](http://wiki.commonjs.org/wiki/Binary/B). This file is included in ink-strings
* primarily to support the base64 encoding methods in {@link module:ink/strings/base64}. But they are still a valuable
* set of tools if you need 'em.
* @copyright Copyright 2012 Hannes Wallnoefer <hannes@helma.at>
* @author Hannes Wallnoefer <hannes@helma.at>
* @author Terry Weiss <me@terryweiss.net>
* @module ink/strings/binary
*/
/**
* The valid set of encoding supported by this library
* @dict
* @property {string} US-ASCII 'ascii'
* @property {string} UTF-8 'utf8'
* @property {string} ISO-10646-UCS-2 'ucs2'
*/
var encodings = exports.encodings = {
'US-ASCII' : 'ascii',
'UTF-8' : 'utf8',
'ISO-10646-UCS-2' : 'ucs2'
};
/**
* Abstract base class for {@link ByteArray} and {@link ByteString}
*
* @constructor
* @abstract
*/
var Binary = exports.Binary = function () {};
/**
* Constructs a writable and growable byte array.
*
* If the first argument to this constructor is a number, it specifies the
* initial length of the ByteArray in bytes.
*
* Else, the argument defines the content of the ByteArray. If the argument is a
* String, the constructor requires a second argument containing the name of the
* String's encoding. If called without arguments, an empty ByteArray is
* returned.
*
* The constructor also accepts a
* [Node.js Buffer](http://nodejs.org/api/buffer.html).
*
* Also, if you pass in Number as the first argument and "false" as the second,
* then the newly created array will not be cleared.
*
* @param {Binary|Array|String|Number|Buffer} contentOrLength Content or length
* of the ByteArray.
* @param {String=} charset The encoding name if the first argument is a
* String.
* @constructor
* @augments module:ink/strings/binary~Binary
*/
var ByteArray = exports.ByteArray = function () {
if ( !(this instanceof ByteArray) ) {
if ( arguments.length === 0 ) {return new ByteArray();}
if ( arguments.length === 1 ) {return new ByteArray( arguments[0] );}
if ( arguments.length === 2 ) {return new ByteArray( arguments[0], arguments[1] );}
}
// ByteArray() - construct an empty byte string
if ( arguments.length === 0 ) {
this.buffer = new Buffer( 0 );
}
// ByteArray(length) - create ByteArray of specified size
else if ( arguments.length === 1 && typeof arguments[0] === "number" ) {
this.buffer = new Buffer( arguments[0] );
this.buffer.fill( 0 );
}
// ByteArray(length) - create ByteArray of specified size, but don't clear
else if ( arguments.length === 2 && typeof arguments[0] === "number" && typeof arguments[1] === "boolean" ) {
this.buffer = new Buffer( arguments[0] );
if ( arguments[1] === true ) {
this.buffer.fill( 0 );
}
}
// ByteArray(byteString or byteArray) - use the contents of byteString or
// byteArray
else if ( arguments.length === 1 && (arguments[0] instanceof ByteString || arguments[0] instanceof ByteArray) ) {
var source = arguments[0];
this.buffer = new Buffer( source.length );
source.buffer.copy( this.buffer );
}
// ByteArray(arrayOfNumbers) - use the numbers in arrayOfNumbers as the bytes
else if ( arguments.length === 1 && Array.isArray( arguments[0] ) ) {
this.buffer = new Buffer( arguments[0] );
}
// ByteArray(buffer) - use contents of buffer
else if ( arguments.length === 1 && arguments[0] instanceof Buffer ) {
var buffer = arguments[0];
this.buffer = new Buffer( buffer.length );
buffer.copy( this.buffer );
}
// ByteArray(string, charset) - convert a string - the ByteArray will contain
// string encoded with charset
else if ( (arguments.length === 1 || (arguments.length === 2 && arguments[1] === undefined)) && typeof arguments[0] === "string" ) {
this.buffer = new Buffer( arguments[0] );
} else if ( arguments.length === 2 && typeof arguments[0] === "string" && typeof arguments[1] === "string" ) {
this.buffer = new Buffer( arguments[0], encodings[arguments[1]] );
} else {
throw new Error( "Illegal arguments to ByteArray constructor" );
}
return this;
};
ByteArray.prototype = new Binary();
Object.defineProperty( ByteArray.prototype, 'length', {
get : function () {
return this.buffer.length;
},
set : function ( length ) {
var buffer = new Buffer( length );
length = Math.min( this.buffer.length, length );
this.buffer.copy( buffer, 0, 0, length );
buffer.fill( 0, length );
this.buffer = buffer;
}
} );
/**
* Constructs an immutable byte string.
*
* If the first argument is a String, the constructor requires a second argument
* containing the name of the String's encoding. If called without arguments, an
* empty ByteString is returned.
*
* The constructor also accepts a
* [Node.js Buffer](http://nodejs.org/api/buffer.html).
*
* @param {Binary|Array|String|Buffer} content The content of the ByteString.
* @param {String=} charset The encoding name if the first argument is a String.
* @constructor
* @augments module:ink/strings/binary~Binary
*/
var ByteString = exports.ByteString = function () {
if ( !(this instanceof ByteString) ) {
if ( arguments.length === 0 ) {return new ByteString();}
if ( arguments.length === 1 ) {return new ByteString( arguments[0] );}
if ( arguments.length === 2 ) {return new ByteString( arguments[0], arguments[1] );}
}
// ByteString() - construct an empty byte string
if ( arguments.length === 0 ) {
this.buffer = new Buffer( 0 );
}
// ByteString(byteString) - returns byteString, which is immutable
else if ( arguments.length === 1 && arguments[0] instanceof ByteString ) {
return arguments[0];
}
// ByteString(byteArray) - use the contents of byteArray
else if ( arguments.length === 1 && arguments[0] instanceof ByteArray ) {
var source = arguments[0];
this.buffer = new Buffer( source.length );
source.buffer.copy( this.buffer );
}
// ByteString(buffer) - use contents of buffer
else if ( arguments.length === 1 && arguments[0] instanceof Buffer ) {
var buffer = arguments[0];
this.buffer = new Buffer( buffer.length );
buffer.copy( this.buffer );
}
// ByteString(arrayOfNumbers) - use the numbers in arrayOfNumbers as the bytes
else if ( arguments.length === 1 && Array.isArray( arguments[0] ) ) {
this.buffer = new Buffer( arguments[0] );
}
// ByteString(string, charset) - convert a string - the ByteString will
// contain string encoded with charset
else if ( (arguments.length === 1 || (arguments.length === 2 && arguments[1] === undefined)) && typeof arguments[0] === "string" ) {
this.buffer = new Buffer( arguments[0] );
} else if ( arguments.length === 2 && typeof arguments[0] === "string" && typeof arguments[1] === "string" ) {
this.buffer = new Buffer( arguments[0], encodings[arguments[1]] );
} else {
throw new Error( "Illegal arguments to ByteString constructor" + JSON.stringify( arguments ) );
}
return this;
};
ByteString.prototype = new Binary();
/**
* Returns the length of the byte string
* @type {integer}
* @memberof module:ink/strings~ByteString
* @name length
*/
Object.defineProperty( ByteString.prototype, 'length', {
get : function () {
return this.buffer.length;
},
set : function () {}
} );
/**
* Converts the String to a mutable ByteArray using the specified encoding.
* @param {string} str The string to convert
* @param {String} charset the name of the string encoding. Defaults to 'UTF-8'
* @returns {ByteArray} a ByteArray representing the string
*/
exports.toByteArray = function ( str, charset ) {
charset = charset || 'UTF-8';
return new ByteArray( String( str ), charset );
};
/**
* Converts the String to an immutable ByteString using the specified encoding.
* @param {string} str The string to convert
* @param {String} charset the name of the string encoding. Defaults to 'UTF-8'
* @returns {ByteString} a ByteString representing the string
*/
exports.toByteString = function ( str, charset ) {
charset = charset || 'UTF-8';
return new ByteString( String( str ), charset );
};
/**
* Reverses the content of the ByteArray in-place
*
* @returns {ByteArray} this ByteArray with its elements reversed
*/
ByteArray.prototype.reverse = function () {
// "limit" is halfway, rounded down. "top" is the last index.
var limit = Math.floor( this.length / 2 ),
top = this.length - 1;
// swap each pair of bytes, up to the halfway point
for ( var i = 0; i < limit; i++ ) {
var tmp = this.buffer[i];
this.buffer[i] = this.buffer[top - i];
this.buffer[top - i] = tmp;
}
return this;
};
/**
* Sorts the content of the ByteArray in-place.
* @private
* @param {Function} comparator the function to compare entries
* @returns {ByteArray} this ByteArray with its elements sorted
*/
var numericCompareFunction = function ( o1, o2 ) {
return o1 - o2;
};
/**
* Sorts a ByteArray.
* @param {function(byte)=} compareFunction A function to be executed against each element in the ByteArray
* If not present, the bytes are compared numerically. Otherwise
* it works like a regular JS array sorter
*
*/
ByteArray.prototype.sort = function ( compareFunction ) {
// TODO: inefficient, optimize
var array = this.toArray();
if ( arguments.length ) {array.sort( compareFunction );}
else {array.sort( numericCompareFunction );}
for ( var i = 0; i < array.length; i++ ) {this.set( i, array[i] );}
};
/**
* Apply a function for each element in the ByteArray.
*
* @param {function(byte)} fn the function to call for each element
* @param {Object=} thisObj optional this-object for callback
*/
ByteArray.prototype.forEach = function ( callback, thisObject ) {
for ( var i = 0, length = this.length; i < length; i++ ) {callback.apply( thisObject, [this.get( i ), i, this] );}
};
/**
* Return a ByteArray containing the elements of this ByteArray for which the
* callback function returns true.
*
* @param {function(byte)} callback the filter function
* @param {Object=} thisObj optional this-object for callback
* @returns {ByteArray} a new ByteArray
*/
ByteArray.prototype.filter = function ( callback, thisObject ) {
var result = new ByteArray( this.length );
for ( var i = 0, length = this.length; i < length; i++ ) {
var value = this.get( i );
if ( callback.apply( thisObject, [value, i, this] ) ) {result.push( value );}
}
return result;
};
/**
* Tests whether some element in the array passes the test implemented by the
* provided function.
*
* @param {function(byte)} callback the callback function
* @param {Object=} thisObj optional this-object for callback
* @returns {Boolean} true if at least one invocation of callback returns true
*/
ByteArray.prototype.some = function ( callback, thisObject ) {
for ( var i = 0, length = this.length; i < length; i++ ) {if ( callback.apply( thisObject, [this.get( i ), i, this] ) ) {return true;}}
return false;
};
/**
* Tests whether all elements in the array pass the test implemented by the
* provided function.
*
* @param {function(byte)} callback the callback function
* @param {Object=} thisObj optional this-object for callback
* @returns {Boolean} true if every invocation of callback returns true
*/
ByteArray.prototype.every = function ( callback, thisObject ) {
for ( var i = 0, length = this._length; i < length; i++ ) {if ( !callback.apply( thisObject, [this.get( i ), i, this] ) ) { return false;}}
return true;
};
/**
* Returns a new ByteArray whose content is the result of calling the provided
* function with every element of the original ByteArray
*
* @param {function(byte)} callback the callback
* @param {Object=} thisObj optional this-object for callback
* @returns {ByteArray} a new ByteArray
*/
ByteArray.prototype.map = function ( callback, thisObject ) {
var result = new ByteArray( this.length );
for ( var i = 0, length = this.length; i < length; i++ ) {result.set( i, callback.apply( thisObject, [this.get( i ), i, this] ) );}
return result;
};
/**
* Apply a function to each element in this ByteArray as to reduce its content
* to a single value.
*
* @param {function(byte)} callback the function to call with each element of the
* ByteArray
* @param {*=} initialValue optional argument to be used as the first argument to the
* first call to the callback
* @returns {*} the return value of the last callback invocation
* @see https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/reduce
*/
ByteArray.prototype.reduce = function ( callback, initialValue ) {
var value = initialValue;
for ( var i = 0, length = this.length; i < length; i++ ) {value = callback( value, this.get( i ), i, this );}
return value;
};
/**
* Apply a function to each element in this ByteArray starting at the last
* element as to reduce its content to a single value.
*
* @param {function(byte)} callback the function to call with each element of the
* ByteArray
* @param {*=} initialValue optional argument to be used as the first argument to the
* first call to the callback
* @returns {*} the return value of the last callback invocation
* @see ByteArray.prototype.reduce
* @see https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/reduceRight
*/
ByteArray.prototype.reduceRight = function ( callback, initialValue ) {
var value = initialValue;
for ( var i = this.length - 1; i > 0; i-- ) {value = callback( value, this.get( i ), i, this );}
return value;
};
/**
* Removes the last element from an array and returns that element.
*
* @returns {Number}
*/
ByteArray.prototype.pop = function () {
if ( this.length === 0 ) {return undefined;}
var last = this.buffer[this.length - 1];
// TODO sort this out as it's very expensive, maybe somehow use
// https://github.com/substack/node-buffers? alternatively could just
// deprecate this
this.length--;
return last;
};
/**
* Appends the given elements and returns the new length of the array.
*
* @param {...Number} num... one or more numbers to append
* @returns {Number} the new length of the ByteArray
*/
ByteArray.prototype.push = function () {
var length, newLength = this.length += length = arguments.length;
try {
for ( var i = 0; i < length; i++ ) {this.set( newLength - length + i, arguments[i] );}
} catch ( e ) {
this.length -= length;
throw e;
}
return newLength;
};
/**
* Removes the first element from the ByteArray and returns that element. This
* method changes the length of the ByteArray
*
* @returns {Number} the removed first element
*/
ByteArray.prototype.shift = function () {
if ( this.length === 0 ) { return undefined;}
var first = this.buffer[0];
var buffer = new Buffer( this.length - 1 );
this.buffer.copy( buffer, 0, 1 );
this.buffer = buffer;
return first;
};
/**
* Adds one or more elements to the beginning of the ByteArray and returns its
* new length.
*
* @param {...Number} num... one or more numbers to append
* @returns {Number} the new length of the ByteArray
*/
ByteArray.prototype.unshift = function () {
var copy = this.slice();
this.length = 0;
try {
this.push.apply( this, arguments );
this.push.apply( this, copy.toArray() );
return this.length;
} catch ( e ) {
this.length = copy.length;
copy.buffer.copy( this.buffer );
throw e;
}
};
/**
* Changes the content of the ByteArray, adding new elements while removing old
* elements.
*
* @param {Number} index the index at which to start changing the ByteArray
* @param {Number} howMany The number of elements to remove at the given
* position
* @param {...Number} elements... the new elements to add at the given position
*/
ByteArray.prototype.splice = function ( index, howMany ) {
if ( index === undefined ) {return;}
if ( index < 0 ) {index += this.length;}
if ( howMany === undefined ) {howMany = this.length - index;}
var end = index + howMany;
var remove = this.slice( index, end );
var keep = this.slice( end );
var inject = Array.prototype.slice.call( arguments, 2 );
this.length = index;
this.push.apply( this, inject );
this.push.apply( this, keep.toArray() );
return remove;
};
/**
* Copy a range of bytes between start and stop from this object to another
* ByteArray at the given target offset.
*
* @param {Number} start
* @param {Number} end
* @param {ByteArray} target
* @param {Number} targetOffset
* @name ByteArray.prototype.copy
* @function
*/
ByteArray.prototype.copy = function ( start, end, target, targetOffset ) {
//TODO validate parameters
target.length = targetOffset + (end - start);
this.buffer.copy( target.buffer, targetOffset, start, end );
};
/**
* The length in bytes. This property is writable. Setting it to a value higher
* than the current value fills the new slots with 0, setting it to a lower
* value truncates the byte array.
*
* @type Number
* @name ByteArray.prototype.length
*/
/**
* Returns a new ByteArray containing a portion of this ByteArray.
*
* @param {Number} begin Zero-based index at which to begin extraction. As a
* negative index, begin indicates an offset from the end of the sequence.
* @param {Number} end Zero-based index at which to end extraction. slice
* extracts up to but not including end. As a negative index, end indicates an
* offset from the end of the sequence. If end is omitted, slice extracts to the
* end of the sequence.
* @returns {ByteArray} a new ByteArray
*/
ByteArray.prototype.slice = function () {
return new ByteArray( ByteString.prototype.slice.apply( this, arguments ) );
};
/**
* Returns a ByteArray composed of itself concatenated with the given
* ByteString, ByteArray, and Array values.
*
* @param {Binary|Array} arg... one or more elements to concatenate
* @returns {ByteArray} a new ByteArray
*/
ByteArray.prototype.concat = function () {
var components = [this],
totalLength = this.length;
for ( var i = 0; i < arguments.length; i++ ) {
var component = Array.isArray( arguments[i] ) ? arguments[i] : [arguments[i]];
for ( var j = 0; j < component.length; j++ ) {
var subcomponent = component[j];
if ( !(subcomponent instanceof ByteString) && !(subcomponent instanceof ByteArray) ) {throw "Arguments to ByteArray.concat() must be ByteStrings, ByteArrays, or Arrays of those.";}
components.push( subcomponent );
totalLength += subcomponent.length;
}
}
var result = new ByteArray( totalLength ),
offset = 0;
components.forEach( function ( component ) {
component.buffer.copy( result.buffer, offset );
offset += component.length;
} );
return result;
};
/**
* Returns a ByteArray
* @return {ByteArray}
*/
ByteArray.prototype.toByteArray = function () {
return new ByteArray( this.buffer );
};
/**
*Returns a ByteString
* @return {ByteString}
*/
ByteArray.prototype.toByteString = function () {
return new ByteString( this.buffer );
};
/**
* Returns an array containing the bytes as numbers.
*
* @name ByteArray.prototype.toArray
* @function
* @returns {array.<byte>}
*/
/**
* Returns a String representation of the ByteArray.
*
* @param {string=} charset The charset to use to create the string
@return {string}
*/
ByteArray.prototype.toString = function ( charset ) {
if ( charset ) {return this.decodeToString( charset );}
return "[ByteArray " + this.length + "]";
};
/**
* Returns the ByteArray decoded to a String using the given encoding
*
* @param {String} encoding the name of the encoding to use
* @name ByteArray.prototype.decodeToString
* @function
* @return {String}
*/
/**
* Returns the index of the first occurrence of sequence (a Number or a
* ByteString or ByteArray of any length) or -1 if none was found. If start
* and/or stop are specified, only elements between the indexes start and stop
* are searched.
*
* @param {Number|Binary} sequence the number or binary to look for
* @param {Number} start optional index position at which to start searching
* @param {Number} stop optional index position at which to stop searching
* @returns {Number} the index of the first occurrence of sequence, or -1
* @name ByteArray.prototype.indexOf
* @function
*
*/
/**
* Returns the index of the last occurrence of sequence (a Number or a
* ByteString or ByteArray of any length) or -1 if none was found. If start
* and/or stop are specified, only elements between the indexes start and stop
* are searched.
*
* @param {Number|Binary} sequence the number or binary to look for
* @param {Number} start optional index position at which to start searching
* @param {Number} stop optional index position at which to stop searching
* @returns {Number} the index of the last occurrence of sequence, or -1
* @name ByteArray.prototype.lastIndexOf
* @function
*/
/**
* Split at delimiter, which can by a Number, a ByteString, a ByteArray or an
* Array of the prior (containing multiple delimiters, i.e., "split at any of
* these delimiters"). Delimiters can have arbitrary size.
*
* @param {Number|Binary} delimiter one or more delimiter items
* @param {Object} options optional object parameter with the following optional
* properties:
* <ul>
* <li>count - Maximum number of elements (ignoring delimiters) to return. The
* last returned element may contain delimiters.</li>
* <li>includeDelimiter - Whether the delimiter should be included in the
* result.</li>
* </ul>
@return {array.<ByteArray>}
*/
ByteArray.prototype.split = function () {
var components = ByteString.prototype.split.apply( this.toByteString(), arguments );
// convert ByteStrings to ByteArrays
for ( var i = 0; i < components.length; i++ ) {
components[i] = new ByteArray( components[i] );
}
return components;
};
/**
* Returns a byte for byte copy of this immutable ByteString as a mutable
* ByteArray.
*
@return {ByteArray}
*/
ByteString.prototype.toByteArray = function () {
return new ByteArray( this );
};
/**
* Returns this ByteString itself.
*
@return {ByteString}
*/
ByteString.prototype.toByteString = function () {
return this;
};
/**
* Returns an array containing the bytes as numbers.
* @param {String} charset optional the name of the string encoding
* @return {array}
*/
Binary.prototype.toArray = function ( charset ) {
if ( charset ) {
return Array.prototype.map.call( this.buffer.toString( encodings[charset] ), function ( x ) {
return x.charCodeAt( 0 );
} );
} else {
return Array.prototype.slice.call( this.buffer, 0 );
}
};
/**
* Returns a debug representation such as `"[ByteString 10]"` where 10 is the
* length of this ByteString.
*
@return {string}
*/
ByteString.prototype.toString = function () {
return '[ByteString ' + this.buffer.length + ']';
};
/**
* Returns this ByteString as string, decoded using the given charset.
*
* @name ByteString.prototype.decodeToString
* @param {String=} charset the name of the string encoding
@return {string}
*/
Binary.prototype.decodeToString = function ( charset ) {
return this.buffer.toString( encodings[charset] );
};
/**
* Returns the index of the first occurrence of sequence (a Number or a
* ByteString or ByteArray of any length), or -1 if none was found. If start
* and/or stop are specified, only elements between the indexes start and stop
* are searched.
*
* @param {Number|Binary} sequence the number or binary to look for
* @param {Number} start optional index position at which to start searching
* @param {Number} stop optional index position at which to stop searching
* @returns {Number} the index of the first occurrence of sequence, or -1
*/
Binary.prototype.indexOf = function ( byteValue, start, stop ) {
// HACK: use ByteString's slice since we know we won't be modifying result
var array = ByteString.prototype.slice.apply( this, [start, stop] ).toArray(),
result = array.indexOf( byteValue );
return (result < 0) ? -1 : result + (start || 0);
};
/**
* Returns the index of the last occurrence of sequence (a Number or a
* ByteString or ByteArray of any length) or -1 if none was found. If start
* and/or stop are specified, only elements between the indexes start and stop
* are searched.
*
* @param {Number|Binary} sequence the number or binary to look for
* @param {Number} start optional index position at which to start searching
* @param {Number} stop optional index position at which to stop searching
* @returns {Number} the index of the last occurrence of sequence, or -1
*/
Binary.prototype.lastIndexOf = function ( byteValue, start, stop ) {
// HACK: use ByteString's slice since we know we won't be modifying result
var array = ByteString.prototype.slice.apply( this, [start, stop] ).toArray(),
result = array.lastIndexOf( byteValue );
return (result < 0) ? -1 : result + (start || 0);
};
/**
* Returns the byte at the given offset as a ByteString.
*
* @param {Number} offset
* @returns {Number}
@function
*/
ByteString.prototype.byteAt = ByteString.prototype.charAt = function ( offset ) {
if ( offset < 0 || offset >= this.buffer.length ) {return new ByteString();}
return new ByteString( [this.buffer[offset]] );
};
/**
* Returns the byte at the given offset as a ByteString.
*
* @param {Number} offset
* @returns {Number}
@method
*/
ByteArray.prototype.byteAt = function ( offset ) {
if ( offset < 0 || offset >= this.buffer.length ) {return new ByteArray();}
return new ByteArray( [this.buffer[offset]] );
};
/**
* Returns the byte at the given offset.
*
* @param {Number} offset
* @returns {ByteString}
@method
*/
Binary.prototype.get = ByteString.prototype.charCodeAt = function ( offset ) {
return this.buffer[offset];
};
ByteArray.prototype.set = function ( offset, value ) {
this.buffer[offset] = value;
};
/**
* Copy a range of bytes between start and stop from this ByteString to a target
* ByteArray at the given targetStart offset.
*
* @param {Number} start
* @param {Number} end
* @param {ByteArray} target
* @param {Number} targetStart
*/
ByteString.prototype.copy = function ( start, end, target, targetOffset ) {
//TODO validate parameters
target.length = targetOffset + (end - start);
this.buffer.copy( target.buffer, targetOffset, start, end );
};
/**
* Split at delimiter, which can by a Number, a ByteString, a ByteArray or an
* Array of the prior (containing multiple delimiters, i.e., "split at any of
* these delimiters"). Delimiters can have arbitrary size.
*
* @param {Number|Binary} delimiter one or more delimiter items
* @param {Object} options optional object parameter with the following optional
* properties:
* <ul>
* <li>count - Maximum number of elements (ignoring delimiters) to return. The
* last returned element may contain delimiters.</li>
* <li>includeDelimiter - Whether the delimiter should be included in the
* result.</li>
* </ul>
@return {array}
*/
ByteString.prototype.split = function ( delimiters, options ) {
// taken from https://github.com/kriskowal/narwhal-lib/
options = options || {};
var includeDelimiter = options.includeDelimiter || false;
// standardize delimiters into an array of ByteStrings:
if ( !Array.isArray( delimiters ) ) {delimiters = [delimiters];}
delimiters = delimiters.map( function ( delimiter ) {
if ( typeof delimiter === "number" ) {
delimiter = [delimiter];
}
return new ByteString( delimiter );
} );
var components = [],
startOffset = 0,
currentOffset = 0;
// loop until there's no more bytes to consume
bytes_loop: while ( currentOffset < this.length ) {
// try each delimiter until we find a match
delimiters_loop: for ( var i = 0; i < delimiters.length; i++ ) {
var d = delimiters[i];
if ( !d.length ) {
currentOffset = this.length;
continue bytes_loop;
}
for ( var j = 0; j < d.length; j++ ) {
// reached the end of the bytes, OR bytes not equal
if ( currentOffset + j > this.length || this.buffer[currentOffset + j] !== d.buffer[j] ) {
continue delimiters_loop;
}
}
// push the part before the delimiter
components.push( this.slice( startOffset, currentOffset ) );
// optionally push the delimiter
if ( includeDelimiter ) {components.push( this.slice( currentOffset, currentOffset + d.length ) );}
// reset the offsets
startOffset = currentOffset = currentOffset + d.length;
continue bytes_loop;
}
// if there was no match, increment currentOffset to try the next one
currentOffset++;
}
// push the remaining part, if any
if ( currentOffset > startOffset ) {components.push( this.slice( startOffset, currentOffset ) );}
return components;
};
/**
* Returns a new ByteString containing a portion of this ByteString.
*
* @param {Number} begin Zero-based index at which to begin extraction. As a
* negative index, begin indicates an offset from the end of the sequence.
* @param {Number} end Zero-based index at which to end extraction. slice
* extracts up to but not including end. As a negative index, end indicates an
* offset from the end of the sequence. If end is omitted, slice extracts to the
* end of the sequence.
* @returns {ByteString} a new ByteString
*/
ByteString.prototype.slice = function ( begin, end ) {
if ( !begin || typeof begin !== "number" ) {begin = 0;}
var length = this.buffer.length;
if ( begin < 0 ) {begin += length;}
if ( !end ) {end = length;}
if ( typeof end !== "number" ) {end = begin;}
if ( end < 0 ) {end += length;}
if ( begin < 0 ) {begin = 0;}
if ( end < begin ) {end = begin;}
if ( begin > length ) {begin = length;}
if ( end > length ) {end = length;}
var buffer = new Buffer( end - begin );
this.buffer.copy( buffer, 0, begin, end );
return new ByteString( buffer );
};
/**
* Returns a ByteString composed of itself concatenated with the given
* ByteString, ByteArray, and Array values.
*
* @param {Binary|Array} arg... one or more elements to concatenate
* @returns {ByteString} a new ByteString
*/
ByteString.prototype.concat = function ( arg ) {
var components = [this],
totalLength = this.length;
for ( var i = 0; i < arguments.length; i++ ) {
var component = Array.isArray( arguments[i] ) ? arguments[i] : [arguments[i]];
for ( var j = 0; j < component.length; j++ ) {
var subcomponent = component[j];
if ( !(subcomponent instanceof ByteString) && !(subcomponent instanceof ByteArray) ) {throw "Arguments to ByteString.concat() must be ByteStrings, ByteArrays, or Arrays of those.";}
components.push( subcomponent );
totalLength += subcomponent.length;
}
}
var result = new Buffer( totalLength ),
offset = 0;
components.forEach( function ( component ) {
component.buffer.copy( result, offset );
offset += component.length;
} );
return new ByteString( result );
};