TextDecoder.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. var defaultEncoding_1 = require("../encoding/defaultEncoding");
  4. var encoding_factory_1 = require("../encoding/encoding-factory");
  5. var encodings_1 = require("../encoding/encodings");
  6. var finished_1 = require("../encoding/finished");
  7. var terminology_1 = require("../encoding/terminology");
  8. var utilities_1 = require("../encoding/utilities");
  9. var Stream_1 = require("./Stream");
  10. /**
  11. * @constructor
  12. * @param {string=} label The label of the encoding;
  13. * defaults to 'utf-8'.
  14. * @param {Object=} options
  15. */
  16. var TextDecoder = /** @class */ (function () {
  17. function TextDecoder(label, options) {
  18. label = label !== undefined ? String(label) : defaultEncoding_1.DEFAULT_ENCODING;
  19. var optionsMap = utilities_1.ToDictionary(options);
  20. // A TextDecoder object has an associated encoding, decoder,
  21. // stream, ignore BOM flag (initially unset), BOM seen flag
  22. // (initially unset), error mode (initially replacement), and do
  23. // not flush flag (initially unset).
  24. /** @private */
  25. this._encoding = null;
  26. /** @private @type {?Decoder} */
  27. this._decoder = null;
  28. /** @private @type {boolean} */
  29. this._ignoreBOM = false;
  30. /** @private @type {boolean} */
  31. this._BOMseen = false;
  32. /** @private @type {string} */
  33. this._error_mode = 'replacement';
  34. /** @private @type {boolean} */
  35. this._do_not_flush = false;
  36. // 1. Let encoding be the result of getting an encoding from
  37. // label.
  38. var encoding = encodings_1.getEncoding(label);
  39. // 2. If encoding is failure or replacement, throw a RangeError.
  40. if (encoding === null || encoding.name === 'replacement')
  41. throw RangeError('Unknown encoding: ' + label);
  42. if (!encoding_factory_1.decoders[encoding.name]) {
  43. throw Error('Decoder not present.' +
  44. ' Did you forget to include encoding-indexes.js first?');
  45. }
  46. // 3. Let dec be a new TextDecoder object.
  47. // const dec = this;
  48. // no need to do this as this is a proper class
  49. // now and TSC will handle transpilation to older platforms
  50. // 4. Set dec's encoding to encoding.
  51. this._encoding = encoding;
  52. // 5. If options's fatal member is true, set dec's error mode to
  53. // fatal.
  54. if (Boolean(optionsMap['fatal']))
  55. this._error_mode = 'fatal';
  56. // 6. If options's ignoreBOM member is true, set dec's ignore BOM
  57. // flag.
  58. if (Boolean(optionsMap['ignoreBOM']))
  59. this._ignoreBOM = true;
  60. // For pre-ES5 runtimes:
  61. // if (!Object.defineProperty) {
  62. // this.encoding = dec._encoding.name.toLowerCase();
  63. // this.fatal = dec._error_mode === 'fatal';
  64. // this.ignoreBOM = dec._ignoreBOM;
  65. // }
  66. // 7. Return dec.
  67. // return dec;
  68. }
  69. Object.defineProperty(TextDecoder.prototype, "encoding", {
  70. // if (Object.defineProperty) {
  71. // The encoding attribute's getter must return encoding's name.
  72. // Object.defineProperty(TextDecoder.prototype, 'encoding', {
  73. // /** @this {TextDecoder} */
  74. // get: function () { return this._encoding.name.toLowerCase(); }
  75. // });
  76. get: function () {
  77. return this._encoding.name.toLowerCase();
  78. },
  79. enumerable: true,
  80. configurable: true
  81. });
  82. Object.defineProperty(TextDecoder.prototype, "fatal", {
  83. // The fatal attribute's getter must return true if error mode
  84. // is fatal, and false otherwise.
  85. // Object.defineProperty(TextDecoder.prototype, 'fatal', {
  86. // /** @this {TextDecoder} */
  87. // get: function () { return this._error_mode === 'fatal'; }
  88. // });
  89. get: function () {
  90. return this._error_mode === 'fatal';
  91. },
  92. enumerable: true,
  93. configurable: true
  94. });
  95. Object.defineProperty(TextDecoder.prototype, "ignoreBOM", {
  96. // The ignoreBOM attribute's getter must return true if ignore
  97. // BOM flag is set, and false otherwise.
  98. // Object.defineProperty(TextDecoder.prototype, 'ignoreBOM', {
  99. // /** @this {TextDecoder} */
  100. // get: function () { return this._ignoreBOM; }
  101. // });
  102. get: function () {
  103. return this._ignoreBOM;
  104. },
  105. enumerable: true,
  106. configurable: true
  107. });
  108. // }
  109. /**
  110. * @param {BufferSource=} input The buffer of bytes to decode.
  111. * @param {Object=} options
  112. * @return {string} The decoded string.
  113. */
  114. TextDecoder.prototype.decode = function (input, options) {
  115. var bytes = getBytesFromInput(input);
  116. var optionsMap = utilities_1.ToDictionary(options);
  117. // 1. If the do not flush flag is unset, set decoder to a new
  118. // encoding's decoder, set stream to a new stream, and unset the
  119. // BOM seen flag.
  120. if (!this._do_not_flush) {
  121. this._decoder = encoding_factory_1.decoders[this._encoding.name]({
  122. fatal: this._error_mode === 'fatal'
  123. });
  124. this._BOMseen = false;
  125. }
  126. // 2. If options's stream is true, set the do not flush flag, and
  127. // unset the do not flush flag otherwise.
  128. this._do_not_flush = Boolean(optionsMap['stream']);
  129. // 3. If input is given, push a copy of input to stream.
  130. // TODO: Align with spec algorithm - maintain stream on instance.
  131. var input_stream = new Stream_1.Stream(bytes);
  132. // 4. Let output be a new stream.
  133. var output = [];
  134. /** @type {?(number|!Array.<number>)} */
  135. var result;
  136. // 5. While true:
  137. while (true) {
  138. // 1. Let token be the result of reading from stream.
  139. var token = input_stream.read();
  140. // 2. If token is end-of-stream and the do not flush flag is
  141. // set, return output, serialized.
  142. // TODO: Align with spec algorithm.
  143. if (token === terminology_1.end_of_stream)
  144. break;
  145. // 3. Otherwise, run these subsubsteps:
  146. // 1. Let result be the result of processing token for decoder,
  147. // stream, output, and error mode.
  148. result = this._decoder.handler(input_stream, token);
  149. // 2. If result is finished, return output, serialized.
  150. if (result === finished_1.finished)
  151. break;
  152. if (result !== null) {
  153. if (Array.isArray(result))
  154. output.push.apply(output, /**@type {!Array.<number>}*/ (result));
  155. else
  156. output.push(result);
  157. }
  158. // 3. Otherwise, if result is error, throw a TypeError.
  159. // (Thrown in handler)
  160. // 4. Otherwise, do nothing.
  161. }
  162. // TODO: Align with spec algorithm.
  163. if (!this._do_not_flush) {
  164. do {
  165. result = this._decoder.handler(input_stream, input_stream.read());
  166. if (result === finished_1.finished)
  167. break;
  168. if (!result)
  169. continue;
  170. if (Array.isArray(result))
  171. output.push.apply(output, /**@type {!Array.<number>}*/ (result));
  172. else
  173. output.push(result);
  174. } while (!input_stream.endOfStream());
  175. this._decoder = null;
  176. }
  177. return this.serializeStream(output);
  178. };
  179. // A TextDecoder object also has an associated serialize stream
  180. // algorithm...
  181. /**
  182. * @param {!Array.<number>} stream
  183. * @return {string}
  184. * @this {TextDecoder}
  185. */
  186. TextDecoder.prototype.serializeStream = function (stream) {
  187. // 1. Let token be the result of reading from stream.
  188. // (Done in-place on array, rather than as a stream)
  189. // 2. If encoding is UTF-8, UTF-16BE, or UTF-16LE, and ignore
  190. // BOM flag and BOM seen flag are unset, run these subsubsteps:
  191. if (utilities_1.includes(['UTF-8', 'UTF-16LE', 'UTF-16BE'], this._encoding.name) &&
  192. !this._ignoreBOM && !this._BOMseen) {
  193. if (stream.length > 0 && stream[0] === 0xFEFF) {
  194. // 1. If token is U+FEFF, set BOM seen flag.
  195. this._BOMseen = true;
  196. stream.shift();
  197. }
  198. else if (stream.length > 0) {
  199. // 2. Otherwise, if token is not end-of-stream, set BOM seen
  200. // flag and append token to stream.
  201. this._BOMseen = true;
  202. }
  203. else {
  204. // 3. Otherwise, if token is not end-of-stream, append token
  205. // to output.
  206. // (no-op)
  207. }
  208. }
  209. // 4. Otherwise, return output.
  210. return utilities_1.codePointsToString(stream);
  211. };
  212. return TextDecoder;
  213. }());
  214. exports.TextDecoder = TextDecoder;
  215. function isBufferInstance(input) {
  216. try {
  217. return input instanceof ArrayBuffer;
  218. }
  219. catch (e) {
  220. console.error(e);
  221. return false;
  222. }
  223. }
  224. function getBytesFromInput(input) {
  225. if (typeof input !== 'object')
  226. return new Uint8Array(0);
  227. if (isBufferInstance(input)) {
  228. return new Uint8Array(input);
  229. }
  230. if ('buffer' in input && isBufferInstance(input.buffer)) {
  231. return new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
  232. }
  233. return new Uint8Array(0);
  234. }
  235. //# sourceMappingURL=TextDecoder.js.map