StringView

The aims of this library are:

  • to create a C-like interface for strings (i.e., an array of character codes — an ArrayBufferView in JavaScript) based upon the JavaScript ArrayBuffer interface
  • to create a highly extensible library that anyone can extend by adding methods to the object StringView.prototype
  • to create a collection of methods for such string-like objects (since now: stringViews) which work strictly on arrays of numbers rather than on creating new immutable JavaScript strings
  • to work with Unicode encodings other than JavaScript's default UTF-16 DOMStrings

Introduction

As web applications become more and more powerful, adding features such as audio and video manipulation, access to raw data using WebSockets, and so forth, it has become clear that there are times when it would be helpful for JavaScript code to be able to quickly and easily manipulate raw binary data. In the past, this had to be simulated by treating the raw data as a string and using the charCodeAt() method to read the bytes from the data buffer.

However, this is slow and error-prone, due to the need for multiple conversions (especially if the binary data is not actually byte-format data, but, for example, 32-bit integers or floats).

JavaScript typed arrays provide a mechanism for accessing raw binary data much more efficiently. The StringView constructor is one level above typed arrays.

The code

stringview.js

JavaScript
"use strict";

/*\
|*|
|*|  :: Number.isInteger() polyfill ::
|*|
|*|  https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger
|*|
\*/

if (!Number.isInteger) {
  Number.isInteger = function isInteger (nVal) {
    return typeof nVal === "number" && isFinite(nVal) && nVal > -9007199254740992 && nVal < 9007199254740992 && Math.floor(nVal) === nVal;
  };
}

/*\
|*|
|*|  StringView - Mozilla Developer Network
|*|
|*|  Revision #8, October 6, 2014
|*|
|*|  https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays/StringView
|*|  https://developer.mozilla.org/en-US/docs/User:fusionchess
|*|
|*|  This framework is released under the GNU Lesser General Public License, version 3 or later.
|*|  http://www.gnu.org/licenses/lgpl-3.0.html
|*|
\*/

function StringView (vInput, sEncoding /* optional (default: UTF-8) */, nOffset /* optional */, nLength /* optional */) {

  var fTAView, aWhole, aRaw, fPutOutptCode, fGetOutptChrSize, nInptLen, nStartIdx = isFinite(nOffset) ? nOffset : 0, nTranscrType = 15;

  if (sEncoding) { this.encoding = sEncoding.toString(); }

  encSwitch: switch (this.encoding) {
    case "UTF-8":
      fPutOutptCode = StringView.putUTF8CharCode;
      fGetOutptChrSize = StringView.getUTF8CharLength;
      fTAView = Uint8Array;
      break encSwitch;
    case "UTF-16":
      fPutOutptCode = StringView.putUTF16CharCode;
      fGetOutptChrSize = StringView.getUTF16CharLength;
      fTAView = Uint16Array;
      break encSwitch;
    case "UTF-32":
      fTAView = Uint32Array;
      nTranscrType &= 14;
      break encSwitch;
    default:
      /* case "ASCII", or case "BinaryString" or unknown cases */
      fTAView = Uint8Array;
      nTranscrType &= 14;
  }

  typeSwitch: switch (typeof vInput) {
    case "string":
      /* the input argument is a primitive string: a new buffer will be created. */
      nTranscrType &= 7;
      break typeSwitch;
    case "object":
      classSwitch: switch (vInput.constructor) {
        case StringView:
          /* the input argument is a stringView: a new buffer will be created. */
          nTranscrType &= 3;
          break typeSwitch;
        case String:
          /* the input argument is an objectified string: a new buffer will be created. */
          nTranscrType &= 7;
          break typeSwitch;
        case ArrayBuffer:
          /* the input argument is an arrayBuffer: the buffer will be shared. */
          aWhole = new fTAView(vInput);
          nInptLen = this.encoding === "UTF-32" ?
              vInput.byteLength >>> 2
            : this.encoding === "UTF-16" ?
              vInput.byteLength >>> 1
            :
              vInput.byteLength;
          aRaw = nStartIdx === 0 && (!isFinite(nLength) || nLength === nInptLen) ?
            aWhole
            : new fTAView(vInput, nStartIdx, !isFinite(nLength) ? nInptLen - nStartIdx : nLength);

          break typeSwitch;
        case Uint32Array:
        case Uint16Array:
        case Uint8Array:
          /* the input argument is a typedArray: the buffer, and possibly the array itself, will be shared. */
          fTAView = vInput.constructor;
          nInptLen = vInput.length;
          aWhole = vInput.byteOffset === 0 && vInput.length === (
            fTAView === Uint32Array ?
              vInput.buffer.byteLength >>> 2
            : fTAView === Uint16Array ?
              vInput.buffer.byteLength >>> 1
            :
              vInput.buffer.byteLength
          ) ? vInput : new fTAView(vInput.buffer);
          aRaw = nStartIdx === 0 && (!isFinite(nLength) || nLength === nInptLen) ?
            vInput
            : vInput.subarray(nStartIdx, isFinite(nLength) ? nStartIdx + nLength : nInptLen);

          break typeSwitch;
        default:
          /* the input argument is an array or another serializable object: a new typedArray will be created. */
          aWhole = new fTAView(vInput);
          nInptLen = aWhole.length;
          aRaw = nStartIdx === 0 && (!isFinite(nLength) || nLength === nInptLen) ?
            aWhole
            : aWhole.subarray(nStartIdx, isFinite(nLength) ? nStartIdx + nLength : nInptLen);
      }
      break typeSwitch;
    default:
      /* the input argument is a number, a boolean or a function: a new typedArray will be created. */
      aWhole = aRaw = new fTAView(Number(vInput) || 0);

  }

  if (nTranscrType < 8) {

    var vSource, nOutptLen, nCharStart, nCharEnd, nEndIdx, fGetInptChrSize, fGetInptChrCode;

    if (nTranscrType & 4) { /* input is string */

      vSource = vInput;
      nOutptLen = nInptLen = vSource.length;
      nTranscrType ^= this.encoding === "UTF-32" ? 0 : 2;
      /* ...or...: nTranscrType ^= Number(this.encoding !== "UTF-32") << 1; */
      nStartIdx = nCharStart = nOffset ? Math.max((nOutptLen + nOffset) % nOutptLen, 0) : 0;
      nEndIdx = nCharEnd = (Number.isInteger(nLength) ? Math.min(Math.max(nLength, 0) + nStartIdx, nOutptLen) : nOutptLen) - 1;

    } else { /* input is stringView */

      vSource = vInput.rawData;
      nInptLen = vInput.makeIndex();
      nStartIdx = nCharStart = nOffset ? Math.max((nInptLen + nOffset) % nInptLen, 0) : 0;
      nOutptLen = Number.isInteger(nLength) ? Math.min(Math.max(nLength, 0), nInptLen - nCharStart) : nInptLen;
      nEndIdx = nCharEnd = nOutptLen + nCharStart;

      if (vInput.encoding === "UTF-8") {
        fGetInptChrSize = StringView.getUTF8CharLength;
        fGetInptChrCode = StringView.loadUTF8CharCode;
      } else if (vInput.encoding === "UTF-16") {
        fGetInptChrSize = StringView.getUTF16CharLength;
        fGetInptChrCode = StringView.loadUTF16CharCode;
      } else {
        nTranscrType &= 1;
      }

    }

    if (nOutptLen === 0 || nTranscrType < 4 && vSource.encoding === this.encoding && nCharStart === 0 && nOutptLen === nInptLen) {

      /* the encoding is the same, the length too and the offset is 0... or the input is empty! */

      nTranscrType = 7;

    }

    conversionSwitch: switch (nTranscrType) {

      case 0:

      /* both the source and the new StringView have a fixed-length encoding... */

        aWhole = new fTAView(nOutptLen);
        for (var nOutptIdx = 0; nOutptIdx < nOutptLen; aWhole[nOutptIdx] = vSource[nStartIdx + nOutptIdx++]);
        break conversionSwitch;

      case 1:

      /* the source has a fixed-length encoding but the new StringView has a variable-length encoding... */

        /* mapping... */

        nOutptLen = 0;

        for (var nInptIdx = nStartIdx; nInptIdx < nEndIdx; nInptIdx++) {
          nOutptLen += fGetOutptChrSize(vSource[nInptIdx]);
        }

        aWhole = new fTAView(nOutptLen);

        /* transcription of the source... */

        for (var nInptIdx = nStartIdx, nOutptIdx = 0; nOutptIdx < nOutptLen; nInptIdx++) {
          nOutptIdx = fPutOutptCode(aWhole, vSource[nInptIdx], nOutptIdx);
        }

        break conversionSwitch;

      case 2:

      /* the source has a variable-length encoding but the new StringView has a fixed-length encoding... */

        /* mapping... */

        nStartIdx = 0;

        var nChrCode;

        for (nChrIdx = 0; nChrIdx < nCharStart; nChrIdx++) {
          nChrCode = fGetInptChrCode(vSource, nStartIdx);
          nStartIdx += fGetInptChrSize(nChrCode);
        }

        aWhole = new fTAView(nOutptLen);

        /* transcription of the source... */

        for (var nInptIdx = nStartIdx, nOutptIdx = 0; nOutptIdx < nOutptLen; nInptIdx += fGetInptChrSize(nChrCode), nOutptIdx++) {
          nChrCode = fGetInptChrCode(vSource, nInptIdx);
          aWhole[nOutptIdx] = nChrCode;
        }

        break conversionSwitch;

      case 3:

      /* both the source and the new StringView have a variable-length encoding... */

        /* mapping... */

        nOutptLen = 0;

        var nChrCode;

        for (var nChrIdx = 0, nInptIdx = 0; nChrIdx < nCharEnd; nInptIdx += fGetInptChrSize(nChrCode)) {
          nChrCode = fGetInptChrCode(vSource, nInptIdx);
          if (nChrIdx === nCharStart) { nStartIdx = nInptIdx; }
          if (++nChrIdx > nCharStart) { nOutptLen += fGetOutptChrSize(nChrCode); }
        }

        aWhole = new fTAView(nOutptLen);

        /* transcription... */

        for (var nInptIdx = nStartIdx, nOutptIdx = 0; nOutptIdx < nOutptLen; nInptIdx += fGetInptChrSize(nChrCode)) {
          nChrCode = fGetInptChrCode(vSource, nInptIdx);
          nOutptIdx = fPutOutptCode(aWhole, nChrCode, nOutptIdx);
        }

        break conversionSwitch;

      case 4:

      /* DOMString to ASCII or BinaryString or other unknown encodings */

        aWhole = new fTAView(nOutptLen);

        /* transcription... */

        for (var nIdx = 0; nIdx < nOutptLen; nIdx++) {
          aWhole[nIdx] = vSource.charCodeAt(nIdx) & 0xff;
        }

        break conversionSwitch;

      case 5:

      /* DOMString to UTF-8 or to UTF-16 */

        /* mapping... */

        nOutptLen = 0;

        for (var nMapIdx = 0; nMapIdx < nInptLen; nMapIdx++) {
          if (nMapIdx === nCharStart) { nStartIdx = nOutptLen; }
          nOutptLen += fGetOutptChrSize(vSource.charCodeAt(nMapIdx));
          if (nMapIdx === nCharEnd) { nEndIdx = nOutptLen; }
        }

        aWhole = new fTAView(nOutptLen);

        /* transcription... */

        for (var nOutptIdx = 0, nChrIdx = 0; nOutptIdx < nOutptLen; nChrIdx++) {
          nOutptIdx = fPutOutptCode(aWhole, vSource.charCodeAt(nChrIdx), nOutptIdx);
        }

        break conversionSwitch;

      case 6:

      /* DOMString to UTF-32 */

        aWhole = new fTAView(nOutptLen);

        /* transcription... */

        for (var nIdx = 0; nIdx < nOutptLen; nIdx++) {
          aWhole[nIdx] = vSource.charCodeAt(nIdx);
        }

        break conversionSwitch;

      case 7:

        aWhole = new fTAView(nOutptLen ? vSource : 0);
        break conversionSwitch;

    }

    aRaw = nTranscrType > 3 && (nStartIdx > 0 || nEndIdx < aWhole.length - 1) ? aWhole.subarray(nStartIdx, nEndIdx) : aWhole;

  }

  this.buffer = aWhole.buffer;
  this.bufferView = aWhole;
  this.rawData = aRaw;

  Object.freeze(this);

}

/* CONSTRUCTOR'S METHODS */

StringView.loadUTF8CharCode = function (aChars, nIdx) {

  var nLen = aChars.length, nPart = aChars[nIdx];

  return nPart > 251 && nPart < 254 && nIdx + 5 < nLen ?
<code class="language-js">      /* (nPart - 252 << 30) may be not safe in ECMAScript! So...: */</code>
      /* six bytes */ (nPart - 252) * 1073741824 + (aChars[nIdx + 1] - 128 << 24) + (aChars[nIdx + 2] - 128 << 18) + (aChars[nIdx + 3] - 128 << 12) + (aChars[nIdx + 4] - 128 << 6) + aChars[nIdx + 5] - 128
    : nPart > 247 && nPart < 252 && nIdx + 4 < nLen ?
      /* five bytes */ (nPart - 248 << 24) + (aChars[nIdx + 1] - 128 << 18) + (aChars[nIdx + 2] - 128 << 12) + (aChars[nIdx + 3] - 128 << 6) + aChars[nIdx + 4] - 128
    : nPart > 239 && nPart < 248 && nIdx + 3 < nLen ?
      /* four bytes */(nPart - 240 << 18) + (aChars[nIdx + 1] - 128 << 12) + (aChars[nIdx + 2] - 128 << 6) + aChars[nIdx + 3] - 128
    : nPart > 223 && nPart < 240 && nIdx + 2 < nLen ?
      /* three bytes */ (nPart - 224 << 12) + (aChars[nIdx + 1] - 128 << 6) + aChars[nIdx + 2] - 128
    : nPart > 191 && nPart < 224 && nIdx + 1 < nLen ?
      /* two bytes */ (nPart - 192 << 6) + aChars[nIdx + 1] - 128
    :
      /* one byte */ nPart;

};

StringView.putUTF8CharCode = function (aTarget, nChar, nPutAt) {

  var nIdx = nPutAt;

  if (nChar < 0x80 /* 128 */) {
    /* one byte */
    aTarget[nIdx++] = nChar;
  } else if (nChar < 0x800 /* 2048 */) {
    /* two bytes */
    aTarget[nIdx++] = 0xc0 /* 192 */ + (nChar >>> 6);
    aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */);
  } else if (nChar < 0x10000 /* 65536 */) {
    /* three bytes */
    aTarget[nIdx++] = 0xe0 /* 224 */ + (nChar >>> 12);
    aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 6) & 0x3f /* 63 */);
    aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */);
  } else if (nChar < 0x200000 /* 2097152 */) {
    /* four bytes */
    aTarget[nIdx++] = 0xf0 /* 240 */ + (nChar >>> 18);
    aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 12) & 0x3f /* 63 */);
    aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 6) & 0x3f /* 63 */);
    aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */);
  } else if (nChar < 0x4000000 /* 67108864 */) {
    /* five bytes */
    aTarget[nIdx++] = 0xf8 /* 248 */ + (nChar >>> 24);
    aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 18) & 0x3f /* 63 */);
    aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 12) & 0x3f /* 63 */);
    aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 6) & 0x3f /* 63 */);
    aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */);
  } else /* if (nChar <= 0x7fffffff) */ { /* 2147483647 */
    /* six bytes */
    aTarget[nIdx++] = 0xfc /* 252 */ + /* (nChar >>> 30) may be not safe in ECMAScript! So...: */ (nChar / 1073741824);
    aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 24) & 0x3f /* 63 */);
    aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 18) & 0x3f /* 63 */);
    aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 12) & 0x3f /* 63 */);
    aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 6) & 0x3f /* 63 */);
    aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */);
  }

  return nIdx;

};

StringView.getUTF8CharLength = function (nChar) {
  return nChar < 0x80 ? 1 : nChar < 0x800 ? 2 : nChar < 0x10000 ? 3 : nChar < 0x200000 ? 4 : nChar < 0x4000000 ? 5 : 6;
};

StringView.loadUTF16CharCode = function (aChars, nIdx) {

  /* UTF-16 to DOMString decoding algorithm */
  var nFrstChr = aChars[nIdx];

  return nFrstChr > 0xD7BF /* 55231 */ && nIdx + 1 < aChars.length ?
    (nFrstChr - 0xD800 /* 55296 */ << 10) + aChars[nIdx + 1] + 0x2400 /* 9216 */
    : nFrstChr;

};

StringView.putUTF16CharCode = function (aTarget, nChar, nPutAt) {

  var nIdx = nPutAt;

  if (nChar < 0x10000 /* 65536 */) {
    /* one element */
    aTarget[nIdx++] = nChar;
  } else {
    /* two elements */
    aTarget[nIdx++] = 0xD7C0 /* 55232 */ + (nChar >>> 10);
    aTarget[nIdx++] = 0xDC00 /* 56320 */ + (nChar & 0x3FF /* 1023 */);
  }

  return nIdx;

};

StringView.getUTF16CharLength = function (nChar) {
  return nChar < 0x10000 ? 1 : 2;
};

/* Array of bytes to base64 string decoding */

StringView.b64ToUint6 = function (nChr) {

  return nChr > 64 && nChr < 91 ?
      nChr - 65
    : nChr > 96 && nChr < 123 ?
      nChr - 71
    : nChr > 47 && nChr < 58 ?
      nChr + 4
    : nChr === 43 ?
      62
    : nChr === 47 ?
      63
    :
      0;

};

StringView.uint6ToB64 = function (nUint6) {

  return nUint6 < 26 ?
      nUint6 + 65
    : nUint6 < 52 ?
      nUint6 + 71
    : nUint6 < 62 ?
      nUint6 - 4
    : nUint6 === 62 ?
      43
    : nUint6 === 63 ?
      47
    :
      65;

};

/* Base64 string to array encoding */

StringView.bytesToBase64 = function (aBytes) {

  var sB64Enc = "";

  for (var nMod3, nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) {
    nMod3 = nIdx % 3;
    /* Uncomment the following line in order to split the output in lines 76-character long: */
    /*
    if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) { sB64Enc += "\r\n"; }
    */
    nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24);
    if (nMod3 === 2 || aBytes.length - nIdx === 1) {
      sB64Enc += String.fromCharCode(StringView.uint6ToB64(nUint24 >>> 18 & 63), StringView.uint6ToB64(nUint24 >>> 12 & 63), StringView.uint6ToB64(nUint24 >>> 6 & 63), StringView.uint6ToB64(nUint24 & 63));
      nUint24 = 0;
    }
  }

  return sB64Enc.replace(/A(?=A$|$)/g, "=");

};


StringView.base64ToBytes = function (sBase64, nBlockBytes) {

  var
    sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length,
    nOutLen = nBlockBytes ? Math.ceil((nInLen * 3 + 1 >>> 2) / nBlockBytes) * nBlockBytes : nInLen * 3 + 1 >>> 2, aBytes = new Uint8Array(nOutLen);

  for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
    nMod4 = nInIdx & 3;
    nUint24 |= StringView.b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4;
    if (nMod4 === 3 || nInLen - nInIdx === 1) {
      for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
        aBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
      }
      nUint24 = 0;
    }
  }

  return aBytes;

};

StringView.makeFromBase64 = function (sB64Inpt, sEncoding, nByteOffset, nLength) {

  return new StringView(sEncoding === "UTF-16" || sEncoding === "UTF-32" ? StringView.base64ToBytes(sB64Inpt, sEncoding === "UTF-16" ? 2 : 4).buffer : StringView.base64ToBytes(sB64Inpt), sEncoding, nByteOffset, nLength);

};

/* DEFAULT VALUES */

StringView.prototype.encoding = "UTF-8"; /* Default encoding... */

/* INSTANCES' METHODS */

StringView.prototype.makeIndex = function (nChrLength, nStartFrom) {

  var

    aTarget = this.rawData, nChrEnd, nRawLength = aTarget.length,
    nStartIdx = nStartFrom || 0, nIdxEnd = nStartIdx, nStopAtChr = isNaN(nChrLength) ? Infinity : nChrLength;

  if (nChrLength + 1 > aTarget.length) { throw new RangeError("StringView.prototype.makeIndex - The offset can\'t be major than the length of the array - 1."); }

  switch (this.encoding) {

    case "UTF-8":

      var nPart;

      for (nChrEnd = 0; nIdxEnd < nRawLength && nChrEnd < nStopAtChr; nChrEnd++) {
        nPart = aTarget[nIdxEnd];
        nIdxEnd += nPart > 251 && nPart < 254 && nIdxEnd + 5 < nRawLength ? 6
          : nPart > 247 && nPart < 252 && nIdxEnd + 4 < nRawLength ? 5
          : nPart > 239 && nPart < 248 && nIdxEnd + 3 < nRawLength ? 4
          : nPart > 223 && nPart < 240 && nIdxEnd + 2 < nRawLength ? 3
          : nPart > 191 && nPart < 224 && nIdxEnd + 1 < nRawLength ? 2
          : 1;
      }

      break;

    case "UTF-16":

      for (nChrEnd = nStartIdx; nIdxEnd < nRawLength && nChrEnd < nStopAtChr; nChrEnd++) {
        nIdxEnd += aTarget[nIdxEnd] > 0xD7BF /* 55231 */ && nIdxEnd + 1 < aTarget.length ? 2 : 1;
      }

      break;

    default:

      nIdxEnd = nChrEnd = isFinite(nChrLength) ? nChrLength : nRawLength - 1;

  }

  if (nChrLength) { return nIdxEnd; }

  return nChrEnd;

};

StringView.prototype.toBase64 = function (bWholeBuffer) {

  return StringView.bytesToBase64(
    bWholeBuffer ?
      (
        this.bufferView.constructor === Uint8Array ?
          this.bufferView
        :
          new Uint8Array(this.buffer)
      )
    : this.rawData.constructor === Uint8Array ?
      this.rawData
    :
      new Uint8Array(this.buffer, this.rawData.byteOffset, this.rawData.length << (this.rawData.constructor === Uint16Array ? 1 : 2))
    );

};

StringView.prototype.subview = function (nCharOffset /* optional */, nCharLength /* optional */) {

  var

    nChrLen, nCharStart, nStrLen, bVariableLen = this.encoding === "UTF-8" || this.encoding === "UTF-16",
    nStartOffset = nCharOffset, nStringLength, nRawLen = this.rawData.length;

  if (nRawLen === 0) {
    return new StringView(this.buffer, this.encoding);
  }

  nStringLength = bVariableLen ? this.makeIndex() : nRawLen;
  nCharStart = nCharOffset ? Math.max((nStringLength + nCharOffset) % nStringLength, 0) : 0;
  nStrLen = Number.isInteger(nCharLength) ? Math.max(nCharLength, 0) + nCharStart > nStringLength ? nStringLength - nCharStart : nCharLength : nStringLength;

  if (nCharStart === 0 && nStrLen === nStringLength) { return this; }

  if (bVariableLen) {
    nStartOffset = this.makeIndex(nCharStart);
    nChrLen = this.makeIndex(nStrLen, nStartOffset) - nStartOffset;
  } else {
    nStartOffset = nCharStart;
    nChrLen = nStrLen - nCharStart;
  }

  if (this.encoding === "UTF-16") {
    nStartOffset <<= 1;
  } else if (this.encoding === "UTF-32") {
    nStartOffset <<= 2;
  }

  return new StringView(this.buffer, this.encoding, nStartOffset, nChrLen);

};

StringView.prototype.forEachChar = function (fCallback, oThat, nChrOffset, nChrLen) {

  var aSource = this.rawData, nRawEnd, nRawIdx;

  if (this.encoding === "UTF-8" || this.encoding === "UTF-16") {

    var fGetInptChrSize, fGetInptChrCode;

    if (this.encoding === "UTF-8") {
      fGetInptChrSize = StringView.getUTF8CharLength;
      fGetInptChrCode = StringView.loadUTF8CharCode;
    } else if (this.encoding === "UTF-16") {
      fGetInptChrSize = StringView.getUTF16CharLength;
      fGetInptChrCode = StringView.loadUTF16CharCode;
    }

    nRawIdx = isFinite(nChrOffset) ? this.makeIndex(nChrOffset) : 0;
    nRawEnd = isFinite(nChrLen) ? this.makeIndex(nChrLen, nRawIdx) : aSource.length;

    for (var nChrCode, nChrIdx = 0; nRawIdx < nRawEnd; nChrIdx++) {
      nChrCode = fGetInptChrCode(aSource, nRawIdx);
      fCallback.call(oThat || null, nChrCode, nChrIdx, nRawIdx, aSource);
      nRawIdx += fGetInptChrSize(nChrCode);
    }

  } else {

    nRawIdx = isFinite(nChrOffset) ? nChrOffset : 0;
    nRawEnd = isFinite(nChrLen) ? nChrLen + nRawIdx : aSource.length;

    for (nRawIdx; nRawIdx < nRawEnd; nRawIdx++) {
      fCallback.call(oThat || null, aSource[nRawIdx], nRawIdx, nRawIdx, aSource);
    }

  }

};

StringView.prototype.valueOf = StringView.prototype.toString = function () {

  if (this.encoding !== "UTF-8" && this.encoding !== "UTF-16") {
    /* ASCII, UTF-32 or BinaryString to DOMString */
    return String.fromCharCode.apply(null, this.rawData);
  }

  var fGetCode, fGetIncr, sView = "";

  if (this.encoding === "UTF-8") {
    fGetIncr = StringView.getUTF8CharLength;
    fGetCode = StringView.loadUTF8CharCode;
  } else if (this.encoding === "UTF-16") {
    fGetIncr = StringView.getUTF16CharLength;
    fGetCode = StringView.loadUTF16CharCode;
  }

  for (var nChr, nLen = this.rawData.length, nIdx = 0; nIdx < nLen; nIdx += fGetIncr(nChr)) {
    nChr = fGetCode(this.rawData, nIdx);
    sView += String.fromCharCode(nChr);
  }

  return sView;

};

Manual

Methods overview

Constructor
stringView new StringView(stringView input, optional DOMString encoding, optional unsigned long startOffset, optional unsigned long length);
stringView new StringView(DOMString input, optional DOMString encoding, optional unsigned long startOffset, optional unsigned long length);
stringView new StringView(unsigned long input);
stringView new StringView(arrayBuffer input, optional DOMString encoding, optional unsigned long byteOffset, optional unsigned long length);
stringView new StringView(arrayBufferView input, optional DOMString encoding, optional unsigned long startOffset, optional unsigned long length);
stringView new StringView(array input, optional DOMString encoding, optional unsigned long startOffset, optional unsigned long length);
Constructor's methods
stringView StringView.makeFromBase64(DOMString base64String, optional DOMString encoding, optional unsigned long byteOffset, optional unsigned long length)
Other constructor's methods
DOMString StringView.bytesToBase64(uint8Array uint8Array);
uint8Array StringView.base64ToBytes(DOMString base64String, optional unsigned char charSize);
unsigned long StringView.loadUTF8CharCode(arrayBufferView typedArray, unsigned long index);
void StringView.putUTF8CharCode(arrayBufferView typedArray, unsigned long charCode, unsigned long index);
unsigned char StringView.getUTF8CharLength(unsigned long charCode);
unsigned long StringView.loadUTF16CharCode(arrayBufferView typedArray, unsigned long index);
void StringView.putUTF16CharCode(arrayBufferView typedArray, unsigned long charCode, unsigned long index);
unsigned char StringView.getUTF16CharLength(unsigned long charCode);
unsigned char StringView.b64ToUint6(unsigned char charCode);
unsigned char StringView.uint6ToB64(unsigned char uint6);
Instances' methods
unsigned long stringView.makeIndex(optional unsigned long charactersLength, optional unsigned long startFrom);
DOMString stringView.toBase64(optional boolean wholeBuffer);
stringView stringView.subview(unsigned long characterOffset, optional unsigned long charactersLength);
void stringView.forEachChar(function callback, optional object thisObject, optional unsigned long characterOffset, optional unsigned long charactersLength);
DOMString stringView.valueOf();
DOMString stringView.toString();

Properties overview

Attribute Type Description
encoding
Read only
DOMString A string expressing the encoding type. For actually supported values see here.
buffer
Read only
arrayBuffer The buffer to be shared between stringView.rawData and stringView.bufferView view references.
rawData
Read only
arrayBufferView An arrayBufferView containing the representation of the string as array of 8-bit, 16-bit, or 32-bit integers (depending on the chosen encoding).
bufferView
Read only
arrayBufferView An arrayBufferView containing the representation of the whole buffer as array of 8-bit, 16-bit, or 32-bit integers (depending on the chosen encoding).

Constructor

Syntax

JavaScript
new StringView(input[, encoding[, startOffset[, length]]])

Description

Create a new string-like object based upon an ArrayBuffer.

Arguments

input (required)
The argument the stringView is constructed from. It can be: If it is a number it will become the length of the new stringView; otherwise it will become its content. The following table shows the behavior of the StringView constructor.
input class stringView.buffer stringView.rawData stringView.bufferView
StringView A new arrayBuffer will be created. Object reference to stringView.bufferView or a new arrayBufferView of stringView.buffer will be created (depending on startOffset and length arguments passed). A new arrayBufferView of stringView.buffer will be created.
DOMString A new arrayBuffer will be created. Object reference to stringView.bufferView or a new arrayBufferView of stringView.buffer will be created (depending on startOffset and length arguments passed). A new arrayBufferView of stringView.buffer will be created.
Number A new arrayBuffer will be created. Object reference to stringView.bufferView. A new arrayBufferView of stringView.buffer will be created.
ArrayBuffer Object reference to input.buffer. Object reference to stringView.bufferView or a new arrayBufferView of stringView.buffer will be created (depending on startOffset and length arguments passed). A new arrayBufferView of stringView.buffer will be created.
ArrayBufferView Object reference to input.buffer. Object reference to input or a new arrayBufferView of the buffer input.buffer (depending on many factors regarding the position of the focused parts). Object reference to input or a new arrayBufferView of the buffer input.buffer (depending on many factors regarding the position of the focused parts).
other ordered object (like Array, collections, etc.) A new arrayBuffer will be created. Object reference to stringView.bufferView or a new arrayBufferView of stringView.buffer will be created (depending on startOffset and length arguments passed). A new arrayBufferView of stringView.buffer will be created.
encoding Optional
A string expressing the encoding type. Possible values are:
  • UTF-8 (default)
  • UTF-16
  • UTF-32
  • Another custom string. In this case it will be treated as ASCII ISO/IEC 8859-15 during conversion to and/or from string. However it is usable also for binary strings.
If not specified (or specified as null) it will default to UTF-8.
startOffset Optional
A number expressing:
  • The start offset of the new stringView in bytes if the input argument is an arrayBuffer
  • The start offset of the new stringView in raw elements if the input argument is a typed array or other ordered object
  • The start offset of the new stringView in codepoints if the input argument is a stringView or a string
If not specified (or specified as null) it will default to 0.
length Optional

A number expressing in codepoints the length of the new stringView if the input argument is a string or a stringView, or in raw elements if the input is a typed array, an arrayBuffer or any other kind of ordered object (like Array, collections, etc.). If not specified it will take the length of the input. It never can be more than the length of the input. If you want to see how to create a stringView bigger than its content, please, see this note.

Examples

JavaScript
var myStringView1 = new StringView("Hello world!"); // UTF-8

alert(myStringView1) // "Hello world!"

var myStringView2 = new StringView(myStringView1, "UTF-16");

alert(myStringView1.buffer.byteLength); // 12
alert(myStringView2.buffer.byteLength); // 24

StringView constructor's methods

makeFromBase64()
Syntax
JavaScript
StringView.makeFromBase64(base64String[, encoding][, byteOffset][, length])
Description
Returns a new instance of StringView constructed decoding a given base64-encoded string.
Arguments
base64String (required)
A base64-encoded string which will be decoded and copied into the new stringView object.
encoding Optional
A string expressing the encoding type. For possible values see here.
byteOffset Optional
A number expressing the start offset in bytes of the new stringView. If not specified (or specified as null) it will be considered as 0.
length Optional
A number expressing the length in codepoints of the new stringView. If not specified it will take the length of the input.
Examples
JavaScript
var myStringView = StringView.makeFromBase64("SGVsbG8gd29ybGQh");
alert(myStringView) // "Hello world!"

This function is useful in order to pass binary data containing strings. See also this example.

StringView instances' properties

encoding
A string expressing the encoding type. For possible values see here.
buffer

The buffer to be shared between stringView.rawData and stringView.bufferView view references.

rawData

An arrayBufferView containing the representation of the string as an array of 8-bit, 16-bit, or 32-bit integers (depending on the chosen encoding).

bufferView

An arrayBufferView containing the representation of the whole buffer as an array of 8-bit, 16-bit, or 32-bit integers (depending on the chosen encoding).

StringView instances' methods

makeIndex()
Syntax
JavaScript
stringView.makeIndex([charactersLength[, startFrom]])
Description
If the charactersLength argument is a number it will be taken as codepoints length and makeIndex() will return the index in elements of that position starting from 0. If the startFrom argument is passed the analysis will be done starting from it. If the charactersLength argument is omitted, makeIndex() will return the length in codepoints (ASCII or UTF-encoded) of the stringView object.
Arguments
charactersLength Optional
A number expressing the distance in codepoints from startFrom of the index of stringView.rawData to be returned.
startFrom Optional
A number expressing the position in raw elements of the characters parts to skip. If omitted it will be considered as 0.
Examples
JavaScript
var myStringView = new StringView("\u6432\u6432\u6432\u6432");
alert(myStringView.makeIndex()) // 4
alert(myStringView.makeIndex(2)) // 6

…using the startFrom argument…

JavaScript
var myStringView = StringView.makeFromBase64("5Lit5paHIGVzcGHDsW9sIEVuZ2xpc2gg4KS54KS/4KSo4KWN4KSm4KWAINin2YTYudix2KjZitipIHBvcnR1Z3XDqnMg4Kas4Ka+4KaC4Kay4Ka+INGA0YPRgdGB0LrQuNC5IOaXpeacrOiqniDgqKrgqbDgqJzgqL7gqKzgqY==");

alert(myStringView);

/* Get the Hindi subview... */
var mySubview1 = myStringView.subview(19, 6);

/* Get the Hindi subview passing an uint8Array as argument... */

var nHindiStart = myStringView.makeIndex(19);
var nHindiEnd = myStringView.makeIndex(6, nHindiStart);
var mySubview2 = new StringView(myStringView.rawData.subarray(nHindiStart, nHindiEnd), "UTF-8");

alert(mySubview1.rawData.length); // 18
alert(mySubview2.rawData.length); // 18

See also: stringView.forEachChar().

Performance note: Each invocation of stringView.makeIndex() runs a cycle for all characters contained in the stringView object between startFrom and startFrom + charactersLength. Dont't use stringView.makeIndex() in a cycle as if it were a normal length property. For custom cycles, look at the example proposed here.
toBase64()
Syntax
JavaScript
stringView.toBase64([wholeBuffer])
Description
Returns a base64-encoded string corresponding to the stringView or to its buffer.
Arguments
wholeBuffer Optional
A boolean expressing whether the returned base64-encoded string will correspond to the whole buffer (true) or to the stringView (false or omitted).
Examples
JavaScript
var myStringView = new StringView("\u6432\u6432\u6432\u6432");
alert(StringView.makeFromBase64(myStringView.toBase64())) // 搲搲搲搲

See also: Base64 encoding and decoding.

subview()
Syntax
JavaScript
stringView.subview(characterOffset[, charactersLength])
Description
Returns a new stringView object which will share the same buffer. Arguments characterOffset and charactersLength will be treated as in String.prototype.substr(characterOffset[, charactersLength) (see). If you want to create a new stringView object cloning without sharing the same buffer, look at this table.
Arguments
characterOffset Optional
A number expressing (in codepoints) the location at which to begin extracting characters.
charactersLength Optional
A number expressing (in codepoints) the location at which to stop extracting characters.
Examples
JavaScript
var myStringView1 = new StringView("Hello world!");
var myStringView2 = myStringView1.subview(3, 7);
alert(myStringView2); // "lo worl"
As was explained above, characterOffset is a character index. The index of the first character is 0, and the index of the last character is 1 less than the length of the stringView. subview begins extracting characters at characterOffset and collects charactersLength characters (unless it reaches the end of the string first, in which case it will return fewer).
  • If characterOffset is positive and is greater than or equal to the length of the string, substr returns an empty string.
  • If characterOffset is negative, substr uses it as a character index from the end of the string. If characterOffset is negative and abs(start) is larger than the length of the string, substr uses 0 as the start index.
  • If charactersLength is 0 or negative, substr returns an empty string. If charactersLength is omitted, substr extracts characters to the end of the string.
forEachChar()
Syntax
JavaScript
stringView.forEachChar(callback[, thisObject[, characterOffset[, charactersLength]]])
Description
Repeatedly invokes a function for each character of the stringView.
Arguments
callback (required)
A function to be invoked for each character. It will be called with four arguments, expressing respectively:
charCode
A number expressing the Unicode representation of the actual character (codepoint).
characterOffset
A number expressing the position, in codepoints, of the passed character.
rawOffset
A number expressing the position, in codepoints parts, of the passed character.
rawDataArray
The array containing the raw data of the stringView.
It will look something like this: callback.call(thisObject, charCode, characterOffset, rawOffset, rawDataArray). If the encoding is a fixed-length Unicode encoding, characterOffset and rawOffset will be the same number.
thisObject Optional
The this object upon which will be executed the callback function.
characterOffset Optional
A number expressing (in codepoints) the location at which to begin executing the callback function.
charactersLength Optional
A number expressing (in codepoints) the number of invocations of the callback function.
Examples
JavaScript
function myCallback (charCode, characterOffset, rawOffset /*, rawDataArray */) {
  this.appendChild(document.createTextNode("char #" + characterOffset + ", raw index: " + rawOffset + ", character: " + String.fromCharCode(charCode)));
  this.appendChild(document.createElement("br"));
}

(new StringView("\u4367\uD889\uDE54\u4343\u5431")).forEachChar(myCallback, document.body);
Note: stringView.forEachChar() executes a complete cycle through all characters in the stringView between characterOffset and characterOffset + charactersLength. If you want to build a custom cycle through a variable-length-encoded stringView (UTF-8, UTF-16), you can use a code like the following, which does not make use of stringView.forEachChar(). If the encoding is a fixed-length one (ASCII, UTF-32, etc.), you can do a normal cycle upon the stringView.rawData array.
JavaScript
var myStringView = new StringView("\u4367\uD889\uDE54\u4343\u5431 – Hello world!"); // an UTF-8-encoded stringView...

alert(myStringView);

for (var nChrCode, nLen = myStringView.rawData.length, nRawIdx = 0, nChrIdx = 0; nRawIdx < nLen; nRawIdx += StringView.getUTF8CharLength(nChrCode), nChrIdx++) {

  nChrCode = StringView.loadUTF8CharCode(myStringView.rawData, nRawIdx);

  /* Do something with each character... */
  alert(String.fromCharCode(nChrCode));

}
Note: For UTF-16, replace getUTF8CharLength() and loadUTF8CharCode() methods respectively with getUTF16CharLength() and loadUTF16CharCode().

See also: stringView.makeIndex() and its note.

valueOf()
Syntax
JavaScript
stringView.valueOf()
Description
Converts stringView's content into string and returns it.
Arguments
none
No arguments expected.
Examples
JavaScript
alert((new StringView("Hello world!")).valueOf() === "Hello world!"); // true

JavaScript calls the stringView.valueOf() method to convert an object to a primitive value. You rarely need to invoke the stringView.valueOf() method yourself; JavaScript automatically invokes it when encountering an object where a primitive value is expected.

toString()
Syntax
JavaScript
stringView.toString()
Description
Converts stringView's content into string and returns it.
Arguments
none
No arguments expected.
Examples
JavaScript
alert((new StringView("Hello world!")).toString() === "Hello world!"); // true

The stringView.toString() method is automatically called when the object is to be represented as a text value or when an object is referred to in a manner in which a string is expected.

Appendix: other StringView constructor's methods (utilities)

bytesToBase64()
Syntax
JavaScript
StringView.bytesToBase64(uint8Array)
Description
Converts an array of bytes (a typed array of Uint8Array subclass) into a base64-encoded string.
Arguments
typedArray (required)
The Uint8Array of bytes to encode to base64.
Examples
JavaScript
alert(StringView.bytesToBase64([72,101,108,108,111,32,119,111,114,108,100,33])); // "SGVsbG8gd29ybGQh"

StringView.bytesToBase64() is useful to encode an UTF-8 string to base64. See this article for other stuff.

base64ToBytes()
Syntax
JavaScript
StringView.base64ToBytes(base64String[, regSize])
Description
Decode a base64 string to an array of bytes and return it. The returned array will be a typed array of Uint8Array subclass.
Arguments
base64String (required)
The string to decode from base64.
regSize Optional
The number of bytes of which the length must result in a multiple (1 or omitted for ASCII, binary strings or UTF-8-encoded strings, 2 for UTF-16 strings, 4 for UTF-32 strings).
Examples
JavaScript
alert(String.fromCharCode.apply(null, StringView.base64ToBytes("SGVsbG8gd29ybGQh"))); // "Hello world!"

StringView.base64ToBytes() is a generic utility useful also for binary data. If you want to pass the StringView.base64ToBytes(base64String[, regSize]).buffer property to an ArrayBufferView subclass different from Uint8Array, you should make use of the regSize argument.

loadUTF8CharCode()
Syntax
JavaScript
StringView.loadUTF8CharCode(typedArray, index)
Description
Returns the single codepoint at the given location from an array of UTF-8-encoded elements. An UTF-8-encoded codepoint can occupy up to six elements. This function will recompose all these parts into a codepoint.
Arguments
typedArray (required)
A typed array containing the UTF-8 encoded elements.
index (required)
The location to be read.
Examples
JavaScript
var myStringView = new StringView("Hello world!"); // an UTF-8 stringView...
alert(StringView.loadUTF8CharCode(myStringView.rawData, 6)); // 119, which is the character code for "w"
StringView.loadUTF8CharCode() is mainly for internal use and generally is of little utility.
putUTF8CharCode()
Syntax
JavaScript
StringView.putUTF8CharCode(typedArray, charCode, index)
Description
Write a single codepoint at the given position into a typed array. A single UTF-8-encoded codepoint can occupy many elements (up to six). This function will split it into the needed parts and will write them. Returns undefined.
Arguments
typedArray (required)
A typed array containing the UTF-8 encoded elements.
charCode (required)
A number expressing the Unicode representation of a character (codepoint).
index (required)
The location to be overwritten.
Examples
JavaScript
var myStringView = new StringView("Hello world!"); // an UTF-8 stringView...
StringView.putUTF8CharCode(myStringView.rawData, "_".charCodeAt(0), 5)
alert(myStringView); // "Hello_world!"

StringView.putUTF8CharCode() is mainly for internal use and generally is of little utility.

getUTF8CharLength()
Syntax
JavaScript
StringView.getUTF8CharLength(charCode)
Description
Returns the number of elements occupied by an UTF-8-encoded codepoint.
Arguments
charCode (required)
A number expressing the Unicode representation of a character (codepoint).
Examples
JavaScript
alert(StringView.getUTF8CharLength("C".charCodeAt(0))); // "C" occupies 1 UTF-8-encoded element...
alert(StringView.getUTF8CharLength("⁈".charCodeAt(0))); // "⁈" occupies 3 UTF-8-encoded elements...

StringView.getUTF8CharLength() is mainly for internal use and generally is of little utility.

loadUTF16CharCode()
Syntax
JavaScript
StringView.loadUTF16CharCode(typedArray, index)
Description
Returns the single codepoint at the given location from an array of UTF-16-encoded elements. An UTF-16 codepoint can occupy up to two UTF-16-encoded elements. This function will recompose all these parts into a codepoint.
Arguments
typedArray (required)
A typed array containing the UTF-16 encoded elements.
index (required)
The location to be read.
Examples
JavaScript
var myStringView = new StringView("Hello world!", "UTF-16"); // an UTF-16 stringView...
alert(StringView.loadUTF16CharCode(myStringView.rawData, 6)); // 119, which is the character code of "w"

StringView.loadUTF16CharCode() is mainly for internal use and generally is of little utility.

putUTF16CharCode()
Syntax
JavaScript
StringView.putUTF16CharCode(typedArray, charCode, index)
Description
Write a single codepoint at the given position into a typed array. A single UTF-16-encoded codepoint can occupy up to two UTF-16-encoded elements. This function will split it into the needed parts and will write them. Returns undefined.
Arguments
typedArray (required)
A typed array containing the UTF-16 encoded elements.
charCode (required)
A number expressing the Unicode representation of a character (codepoint).
index (required)
The location to be overwritten.
Examples
JavaScript
var myStringView = new StringView("Hello world!", "UTF-16"); // an UTF-16 stringView...
StringView.putUTF16CharCode(myStringView.rawData, "_".charCodeAt(0), 5)
alert(myStringView); // "Hello_world!"

StringView.putUTF16CharCode() is mainly for internal use and generally is of little utility.

getUTF16CharLength()
Syntax
JavaScript
StringView.getUTF16CharLength(charCode)
Description
Returns the number of elements occupied by an UTF-16-encoded codepoint.
Arguments
charCode (required)
A number expressing the Unicode representation of a character (codepoint).
Examples
JavaScript
alert("\uD950\uDF21"); // 0x64321
alert(StringView.b64ToUint6(0x64321)); // "\uD950\uDF21" occupies 2 UTF-16-encoded elements...

StringView.getUTF16CharLength() is mainly for internal use and generally is of little utility.

b64ToUint6()
Syntax
JavaScript
StringView.b64ToUint6(charCode)
Description
Returns the 6-bit number corresponding to the given base64 character code.
Arguments
charCode (required)
A number expressing a base64 character code.
Examples
JavaScript
alert(StringView.b64ToUint6("B".charCodeAt(0))); // "B" stands for 1 in base64 encoding...
StringView.b64ToUint6() is mainly for internal use and generally is of little utility.
uint6ToB64()
Syntax
JavaScript
StringView.uint6ToB64(uint6)
Description
Returns the base64 character code corresponding to the given 6-bit number.
Arguments
uint6 (required)
A 6-bit unsigned number (0 = uint6 < 64).
Examples
JavaScript
alert(String.fromCharCode(StringView.uint6ToB64(1))); // 1 is represented by "B" in base64 encoding...

StringView.uint6ToB64() is mainly for internal use and generally is of little utility.

Advanced examples

Edit an ASCII part contained within a binary file

Let's compile this C program:

C
#include <stdio.h>

int main () {
    printf("Hello world!\n");
    return 0;
}

In a 64-bit machine it will result in an output like the following first alert. Let's try to translate the "Hello world!" string into the Italian "Ciao mondo!!"

JavaScript
var swHelloWorld = StringView.makeFromBase64("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AwAAAAAAAAAAAAAAAAAAAAAAAAAYGAAAAAAAAD4CAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAQAAAkAAAAAAAAAAAAAAAAAAAAAAAAMAAgAcAkAAAAAAAAAAAAAAAAAAAAAAAAMAAwA8AkAAAAAAAAAAAAAAAAAAAAAAAAMABABgAkAAAAAAAAAAAAAAAAAAAAAAAAMABQCAAkAAAAAAAAAAAAAAAAAAAAAAAAMABgDgAkAAAAAAAAAAAAAAAAAAAAAAAAMABwAeA0AAAAAAAAAAAAAAAAAAAAAAAAMACAAoA0AAAAAAAAAAAAAAAAAAAAAAAAMACQBIA0AAAAAAAAAAAAAAAAAAAAAAAAMACgBgA0AAAAAAAAAAAAAAAAAAAAAAAAMACwCoA0AAAAAAAAAAAAAAAAAAAAAAAAMADADQA0AAAAAAAAAAAAAAAAAAAAAAAAMADQAQBEAAAAAAAAAAAAAAAAAAAAAAAAMADgCUBUAAAAAAAAAAAAAAAAAAAAAAAAMADwCgBUAAAAAAAAAAAAAAAAAAAAAAAAMAEAC0BUAAAAAAAAAAAAAAAAAAAAAAAAMAEQDoBUAAAAAAAAAAAAAAAAAAAAAAAAMAEgDgBmAAAAAAAAAAAAAAAAAAAAAAAAMAEwDoBmAAAAAAAAAAAAAAAAAAAAAAAAMAFADwBmAAAAAAAAAAAAAAAAAAAAAAAAMAFQD4BmAAAAAAAAAAAAAAAAAAAAAAAAMAFgDICGAAAAAAAAAAAAAAAAAAAAAAAAMAFwDQCGAAAAAAAAAAAAAAAAAAAAAAAAMAGAAACWAAAAAAAAAAAAAAAAAAAAAAAAMAGQAQCWAAAAAAAAAAAAAAAAAAAAAAAAMAGgAAAAAAAAAAAAAAAAAAAAAAAQAAAAQA8f8AAAAAAAAAAAAAAAAAAAAAAAAAAAQA8f8AAAAAAAAAAAAAAAAAAAAACAAAAAQA8f8AAAAAAAAAAAAAAAAAAAAAEwAAAAEAFADwBmAAAAAAAAAAAAAAAAAAIAAAAAIADQBABEAAAAAAAAAAAAAAAAAANQAAAAIADQBwBEAAAAAAAAAAAAAAAAAASAAAAAIADQCwBEAAAAAAAAAAAAAAAAAAXgAAAAEAGQAQCWAAAAAAAAEAAAAAAAAAbQAAAAEAEwDoBmAAAAAAAAAAAAAAAAAAlAAAAAIADQDQBEAAAAAAAAAAAAAAAAAAoAAAAAEAEgDgBmAAAAAAAAAAAAAAAAAAvwAAAAQA8f8AAAAAAAAAAAAAAAAAAAAACAAAAAQA8f8AAAAAAAAAAAAAAAAAAAAAxwAAAAEAEQDYBkAAAAAAAAAAAAAAAAAA1QAAAAEAFADwBmAAAAAAAAAAAAAAAAAAAAAAAAQA8f8AAAAAAAAAAAAAAAAAAAAA4QAAAAAAEgDoBmAAAAAAAAAAAAAAAAAA8gAAAAEAFQD4BmAAAAAAAAAAAAAAAAAA+wAAAAAAEgDgBmAAAAAAAAAAAAAAAAAADgEAAAEAFwDQCGAAAAAAAAAAAAAAAAAAJAEAABIADQCQBUAAAAAAAAIAAAAAAAAANAEAACAAAAAAAAAAAAAAAAAAAAAAAAAAUAEAACAAGAAACWAAAAAAAAAAAAAAAAAAWwEAABIAAAAAAAAAAAAAAAAAAAAAAAAAbQEAABAAGAAQCWAAAAAAAAAAAAAAAAAAdAEAABIADgCUBUAAAAAAAAAAAAAAAAAAegEAABIAAAAAAAAAAAAAAAAAAAAAAAAAmQEAABAAGAAACWAAAAAAAAAAAAAAAAAApgEAACAAAAAAAAAAAAAAAAAAAAAAAAAAtQEAABECGAAICWAAAAAAAAAAAAAAAAAAwgEAABEADwCgBUAAAAAAAAQAAAAAAAAA0QEAABIADQAgBUAAAAAAAGUAAAAAAAAA4QEAABAAGQAYCWAAAAAAAAAAAAAAAAAA5gEAABIADQAQBEAAAAAAAAAAAAAAAAAA7QEAABAAGQAQCWAAAAAAAAAAAAAAAAAA+QEAABIADQAABUAAAAAAABUAAAAAAAAA/gEAACAAAAAAAAAAAAAAAAAAAAAAAAAAEgIAABECGAAQCWAAAAAAAAAAAAAAAAAAHgIAACAAAAAAAAAAAAAAAAAAAAAAAAAAOAIAABIACwCoA0AAAAAAAAAAAAAAAAAAAGluaXQuYwBjcnRzdHVmZi5jAF9fSkNSX0xJU1RfXwBkZXJlZ2lzdGVyX3RtX2Nsb25lcwByZWdpc3Rlcl90bV9jbG9uZXMAX19kb19nbG9iYWxfZHRvcnNfYXV4AGNvbXBsZXRlZC42MzU5AF9fZG9fZ2xvYmFsX2R0b3JzX2F1eF9maW5pX2FycmF5X2VudHJ5AGZyYW1lX2R1bW15AF9fZnJhbWVfZHVtbXlfaW5pdF9hcnJheV9lbnRyeQBoZWxsby5jAF9fRlJBTUVfRU5EX18AX19KQ1JfRU5EX18AX19pbml0X2FycmF5X2VuZABfRFlOQU1JQwBfX2luaXRfYXJyYXlfc3RhcnQAX0dMT0JBTF9PRkZTRVRfVEFCTEVfAF9fbGliY19jc3VfZmluaQBfSVRNX2RlcmVnaXN0ZXJUTUNsb25lVGFibGUAZGF0YV9zdGFydABwdXRzQEBHTElCQ18yLjIuNQBfZWRhdGEAX2ZpbmkAX19saWJjX3N0YXJ0X21haW5AQEdMSUJDXzIuMi41AF9fZGF0YV9zdGFydABfX2dtb25fc3RhcnRfXwBfX2Rzb19oYW5kbGUAX0lPX3N0ZGluX3VzZWQAX19saWJjX2NzdV9pbml0AF9lbmQAX3N0YXJ0AF9fYnNzX3N0YXJ0AG1haW4AX0p2X1JlZ2lzdGVyQ2xhc3NlcwBfX1RNQ19FTkRfXwBfSVRNX3JlZ2lzdGVyVE1DbG9uZVRhYmxlAF9pbml0AA==", "ASCII", 1444, 12);

alert(String.fromCharCode.apply(null, swHelloWorld.bufferView)); // swHelloWorld.buffer contains the whole file
alert(swHelloWorld); // "Hello world"

/* traslating "Hello world" into italian... */

swHelloWorld.rawData.set((new StringView("Ciao mondo!!", "ASCII")).rawData);

/* download the new executable file... */

location.assign(URL.createObjectURL(new Blob([swHelloWorld.buffer], { "type": "application\/x-executable" })));
Note: The executable above has been compiled with GNU GCC 4.8.1 running on a 64-bit machine with Arch GNU/Linux.

Glossary

Element
An item of the backing array (whether Uint8Array, Uint16Array, Uint32Array, etc.)
Codepoint
An unique number for each Unicode character. It is rappresented by a collection of 1-6 uint8 elements for UTF-8, 1-2 uint16 elements for UTF-16, 1 uint32 element for UCS4, 1 uint8 element for ASCII, or something else.
Byte
A collection of 8 bits.
NULL codepoint
The character whose codepoint is equal to 0 ('\0').

Notes

  • When you include the script stringview.js into a page, no variables other than StringView itself will be added to the global scope.
  • StringView is a highly extensible library, that anyone can extend by adding methods to the object StringView.prototype.
    For example, imagine you want to create a method similar to string.replace(), but for stringView objects. Maybe you would like to solve a situation like the following:
    JavaScript
    var oMyStringView = new StringView("Hello,&nbsp;strange&nbsp;people!"); // UTF-8
    var oMyRegExp = new CLikeRegExp("&nbsp;", "g");
    
    alert(oMyStringView.replace(oMyRegExp, " ")); // "Hello, strange people!\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
    
    /* stringView.replace() will be different from string.replace() because it act also on the source: */
    alert(oMyStringView); // "Hello, strange people!\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
    As you can see, the previous example needs you to create two new algorithms: the CLikeRegExp() constructor – a constructor of C-like regular expression objects – and StringView.prototype.replace() – the new method, able to act on stringView instances. Well, just include stringview.js to your scope and work on them in another script:
    JavaScript
    /* A constructor of C-like regular expression objects... */
    function CLikeRegExp (sRegExp, sFlags) {
        /**
        *  [...]
        *
        *  CONSTRUCTOR CODE HERE!
        **/
    }
    
    /* A "replace" method for StringView... */
    StringView.prototype.replace = function (oCLikeRegExp, sWith) {
        /**
        *  [...]
        *
        *  METHOD CODE HERE!
        **/
    };
  • If you want to create a stringView inside a bigger empty buffer, like in the following C line
    C
    /* myBuffer: 12 character followed by 1012 NULLs... */
    char myBuffer[1024] = "Hello world!";
    you should do something like this:
    JavaScript
    var myBuffer = new StringView(1024);
    /* myBuffer: 12 character followed by 1012 NULLs... */
    var myContent = new StringView("Hello world!");
    myBuffer.rawData.set(myContent.rawData, myContent.rawData.length);
  • StringView is a constructor and a collection of methods whose aim is to work strictly on arrays of numbers, rather than on creating new immutable JavaScript strings. Keep it in mind when you try to extend its prototype.
  • Since stringView, unlike C strings, has a length property, there is no reason to add a NULL codepoint ('\0') after the termination of a string.
  • StringView has been proposed as strawman for ES6 on ECMAScript Bugs. Everyone can participate in the discussion at bug 1557 or at esdiscuss.

See also

License

© 2016 Mozilla Contributors
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-us/docs/web/javascript/typed_arrays/stringview

Code snippet Guide JavaScript JavaScript typed arrays js-ctypes MDN JavaScript Libraries Sample code Typed Arrays WebGL WebSockets