2

I have some string need to be a UTF-16 text file. For example:

var s = "aosjdfkzlzkdoaslckjznx";
var file = "data:text/plain;base64," + btoa(s);

This will result a UTF-8 encoding text file. How can I get a UTF-16 text file with string s?

Wilson Luniz
  • 459
  • 2
  • 7
  • 18
  • Does your file need to be in a `data:` URL? 'cause you can easily do what you want with a Blob. – Touffy Oct 04 '15 at 18:50
  • Yes, I need base64. But I don't like fileReaderAPI because it's asynchronous. It will make my app too complex. Do you have better suggestion? – Wilson Luniz Oct 04 '15 at 19:20
  • I suggest you reevaluate your criteria. Web apps use async stuff, it's inevitable. But as it happens, using TextEncoder and creating a Blob URI won't be async. You don't need the File API because you're not reading a file, you're creating one. – Touffy Oct 04 '15 at 20:49
  • I use the arrayBuffer directly convert to base64 with this: http://stackoverflow.com/questions/9267899/arraybuffer-to-base64-encoded-string – Wilson Luniz Oct 04 '15 at 21:01

2 Answers2

13

Related: Javascript to csv export encoding issue

This should do it:

document.getElementById('download').addEventListener('click', function(){

 downloadUtf16('Hello, World', 'myFile.csv')
});

function downloadUtf16(str, filename) {

 // ref: https://stackoverflow.com/q/6226189
 var charCode, byteArray = [];

 // BE BOM
  byteArray.push(254, 255);

 // LE BOM
  // byteArray.push(255, 254);

  for (var i = 0; i < str.length; ++i) {
  
    charCode = str.charCodeAt(i);
    
    // BE Bytes
    byteArray.push((charCode & 0xFF00) >>> 8);
    byteArray.push(charCode & 0xFF);
    
    // LE Bytes
    // byteArray.push(charCode & 0xff);
    // byteArray.push(charCode / 256 >>> 0);
  }
  
  var blob = new Blob([new Uint8Array(byteArray)], {type:'text/plain;charset=UTF-16BE;'});
  var blobUrl = URL.createObjectURL(blob);
  
 // ref: https://stackoverflow.com/a/18197511
  var link = document.createElement('a');
  link.href = blobUrl;
  link.download = filename;

  if (document.createEvent) {
    var event = document.createEvent('MouseEvents');
    event.initEvent('click', true, true);
    link.dispatchEvent(event);
  } else {
    link.click();
  }
}
<button id="download">Download</button>
Community
  • 1
  • 1
skibulk
  • 3,088
  • 1
  • 34
  • 42
  • Does the trick for me! but, in your example you are working with BE BOM, but encode your blob with charset UTF-16LE. Shouldn't that be UTF-16BE then? – LocalHorst May 03 '17 at 10:53
  • You save my life. – thejoin May 02 '18 at 14:59
  • 1
    Does this also work for Unicode surrogate pairs (see the [charCodeAt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt) and [codePointAt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt) documentation), i.e. characters/emojis with a unicode value greater than 0xFFFF? – Sebastian Jul 08 '20 at 07:12
1

You can use a legacy polyfill of the native TextEncoder API to transform a JavaScript string into an ArrayBuffer. As you'll see in that documentation, UTF16 with either endianness is was supported. Libraries that provide UTF-16 support in a Text-Encoder-compatible way will probably appear soon, if they haven't already. Let's assume that one such library exposes a constructor called ExtendedTextEncoder.

Then you can easily create a Blob URL to allow users to download the file, without the inefficient base-64 conversion.

Something like this:

s = "aosjdfkzlzkdoaslckjznx"
var encoder = new ExtendedTextEncoder("utf-16be")
var blob = new Blob(encoder.encode(s), "text/plain")
var url = URL.createObjectURL(blob)

Now you can use url instead of your data: URL.

Touffy
  • 6,309
  • 22
  • 28
  • Just note, UTF-16LE text file has a magic header(FF,FE or 255,254). See: https://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding – Wilson Luniz Oct 04 '15 at 21:03
  • I'm sorry but I found this doesn't works in Safari. Safari just doesn't support TextEncoder() . Is there any other solution for Safari? – Wilson Luniz Oct 10 '15 at 16:41
  • 1
    You can use a [polyfill](https://github.com/inexorabletash/text-encoding) for TextEncoder. I can recommend the one I linked, I've used it myself. It will create a TextEncoder object that behaves just like the native one (only if it's not avaialble natvely). – Touffy Oct 10 '15 at 17:50
  • Oh this is my first time heard about polyfill. Thanks and it works! – Wilson Luniz Oct 11 '15 at 05:58
  • TextEncoder does not accept an encoding string as a parameter any more: https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder/TextEncoder – skibulk Mar 27 '17 at 01:06
  • @skibulk thanks for the notification. I'll update my answer. – Touffy Mar 27 '17 at 15:38