docs: def ReadUnicodeTable(filename, nfields, doline):
    """Generic Unicode table text file reader.
  
    The reader takes care of stripping out comments and also
    parsing the two different ways that the Unicode tables specify
    code ranges (using the .. notation and splitting the range across
    multiple lines).
  
    Each non-comment line in the table is expected to have the given
    number of fields.  The first field is known to be the Unicode value
    and the second field its description.
  
    The reader calls doline(codes, fields) for each entry in the table.
    If fn raises an exception, the reader prints that exception,
    prefixed with the file name and line number, and continues
    processing the file.  When done with the file, the reader re-raises
    the first exception encountered during the file.
  
    Arguments:
      filename: the Unicode data file to read, or a file-like object.
      nfields: the number of expected fields per line in that file.
      doline: the function to call for each table entry.
  
    Raises:
      InputError: nfields is invalid (must be >= 2).
    """
  
    if nfields < 2:
      raise InputError("invalid number of fields %d" % (nfields,))
  
    if type(filename) == str:
      if filename.startswith("http://"):
        fil = urllib2.urlopen(filename)
      else:
        fil = open(filename, "r")
    else:
      fil = filename
  
    first = None        # first code in multiline range
    expect_last = None  # tag expected for "Last" line in multiline range
    lineno = 0          # current line number
    for line in fil:
      lineno += 1
      try:
        # Chop # comments and white space; ignore empty lines.
        sharp = line.find("#")
        if sharp >= 0:
          line = line[:sharp]
        line = line.strip()
        if not line:
          continue
  
        # Split fields on ";", chop more white space.
        # Must have the expected number of fields.
        fields = [s.strip() for s in line.split(";")]
        if len(fields) != nfields:
          raise InputError("wrong number of fields %d %d - %s" %
                           (len(fields), nfields, line))
  
        # The Unicode text files have two different ways
        # to list a Unicode range.  Either the first field is
        # itself a range (0000..FFFF), or the range is split
        # across two lines, with the second field noting
        # the continuation.
        codes = _URange(fields[0])
        (name, cont) = _ParseContinue(fields[1])
  
        if expect_last is not None:
          # If the last line gave the First code in a range,
          # this one had better give the Last one.
          if (len(codes) != 1 or codes[0] <= first or
              cont != "Last" or name != expect_last):
            raise InputError("expected Last line for %s" %
                             (expect_last,))
          codes = range(first, codes[0] + 1)
          first = None
          expect_last = None
          fields[0] = "%04X..%04X" % (codes[0], codes[-1])
          fields[1] = name
        elif cont == "First":
          # Otherwise, if this is the First code in a range,
          # remember it and go to the next line.
          if len(codes) != 1:
            raise InputError("bad First line: range given")
          expect_last = name
          first = codes[0]
          continue
  
        doline(codes, fields)
  
      except Exception, e:
        print "%s:%d: %s" % (filename, lineno, e)
        raise
  
    if expect_last is not None:
      raise InputError("expected Last line for %s; got EOF" %
                       (expect_last,))