Reading textual data

This article describes how to read textual data from streams, files and sockets.

In order to read textual data, you need to know which character encoding the data is in. Files and network sockets contain bytes, not characters - to give these bytes a meaning, you need to know the character encoding.

XXX: Document nsIUnicharStreamListener (gecko 1.8) XXX: Also document nsIStreamListener here?

Determining the character encoding of data

If you have a network channel (nsIChannel), you can try the contentCharset property of it. Note that not all channels know the character encoding of the data. You can fallback to the default character encoding stored in preferences (intl.charset.default, a localized pref value)

When reading from a file, the question is harder to answer. Using the system character encoding may work (XXX insert text how to get it), or again the default character encoding from preferences.

Converting read data

If you read data from nsIScriptableInputStream as described on the file I/O code snippets page, you can convert it to UTF-8

// sstream is nsIScriptableInputStream
var str = sstream.read(4096);
var utf8Converter = Components.classes["@mozilla.org/intl/utf8converterservice;1"].
    getService(Components.interfaces.nsIUTF8ConverterService);
var data = utf8Converter.convertURISpecToUTF8 (str, "UTF-8"); 

Gecko 1.8 and newer

Reading strings

Starting with Gecko 1.8 (SeaMonkey 1.0, Firefox 1.5), you can use nsIConverterInputStream to read strings from a stream (nsIInputStream). This work was done in bug 295047.

Usage:

var charset = /* Need to find out what the character encoding is. Using UTF-8 for this example: */ "UTF-8";
const replacementChar = Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER;
var is = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
                   .createInstance(Components.interfaces.nsIConverterInputStream);
is.init(fis, charset, 1024, replacementChar);

Now you can read string from is:

var str = {};
var numChars = is.readString(4096, str);
if (numChars != 0 /* EOF */)
  var read_string = str.value;

To read the entire stream and do something with the data:

var str = {};
while (is.readString(4096, str) != 0) {
  processData(str.value);
}

Don't forget to close the stream when you're done with it (is.close()). Not doing so can cause problems if you try to rename or delete the file at a later time on some platforms.

Note that you may get less characters than you asked for, especially (but not only) at the end of the file (stream).

Unsupported byte sequences

You can specify what should happen with byte sequences that do not correspond to a valid character. The last (4th) argument to init specifies which character they get replaced with; nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER is U+FFFD REPLACEMENT CHARACTER, which is often a good choice.

If you do not want any replacement, you can specify 0x0000 as replacement character; that way, readString will throw an exception when reaching unsupported bytes.

Reading lines

The nsIUnicharLineInputStream interface provides an easy way to read entire lines from a unichar stream. It can be used like nsILineInputStream, except that it supports non-ASCII characters, and has no problems with charsets with embedded nulls (like UTF-16 and UTF-32).

It can be used like this:

var charset = /* Need to find out what the character encoding is. Using UTF-8 for this example: */ "UTF-8";
var is = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
                   .createInstance(Components.interfaces.nsIConverterInputStream);
// This assumes that fis is the nsIInputStream you want to read from
is.init(fis, charset, 1024, 0xFFFD);
is.QueryInterface(Components.interfaces.nsIUnicharLineInputStream);

if (is instanceof Components.interfaces.nsIUnicharLineInputStream) {
  var line = {};
  var cont;
  do {
    cont = is.readLine(line);

    // Now you can do something with line.value
  } while (cont);
}

The above example reads an entire stream until EOF. See nsIConverterInputStream for nsIConverterInputStream.init() arguments.

Earlier versions

Reading strings

Earlier versions of gecko do not provide easy ways to read unicode data from a stream. You will have to manually read a block of data and convert it using nsIScriptableUnicodeConverter.

For example:

// First, get and initialize the converter
var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
                          .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
converter.charset = /* The character encoding you want, using UTF-8 here */ "UTF-8";

// Now, read from the stream
// This assumes istream is the stream you want to read from
var scriptableStream = Components.classes["@mozilla.org/scriptableinputstream;1"]
                                 .createInstance(Components.interfaces.nsIScriptableInputStream);
scriptableStream.init(istream);
var chunk = scriptableStream.read(4096);
var text = converter.ConvertToUnicode(chunk);

However, you must be aware that this method will not work for character encodings that have embedded null bytes, such as UTF-16 or UTF-32.

Reading Lines

There is no easy, general way to read a unicode line from a stream.

For the limited use case of reading lines from a local file, the following code using nsIScriptableUnicodeConverter works. This code will not work for character encodings that contain embedded nulls such as UTF-16 and UTF-32

// First, get and initialize the converter
var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
                          .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
converter.charset = /* The character encoding you want, using UTF-8 here */ "UTF-8";

// This assumes that 'file' is a variable that contains the file you want to read, as an nsIFile
var fis = Components.classes["@mozilla.org/network/file-input-stream;1"]
                    .createInstance(Components.interfaces.nsIFileInputStream);
fis.init(file, -1, -1, 0);

var lis = fis.QueryInterface(Components.interfaces.nsILineInputStream);
var lineData = {};
var cont;
do {
  cont = lis.readLine(lineData);
  var line = converter.ConvertToUnicode(lineData.value);

  // Now you can do something with line

} while (cont);
fis.close();

See also