2013-04-03 53 views
13

私はバイト配列uto16テキストファイルをgolangの文字列に読み込む方法は?

にファイルを読み込むことができますが、私は、文字列

に変換するときには、それを正しく変換するために、どのようにASCII

としてUTF16バイトを扱いますか?

package main 

import ("fmt" 
"os" 
"bufio" 
) 

func main(){ 
    // read whole the file 
    f, err := os.Open("test.txt") 
    if err != nil { 
     fmt.Printf("error opening file: %v\n",err) 
     os.Exit(1) 
    } 
    r := bufio.NewReader(f) 
    var s,b,e = r.ReadLine() 
    if e==nil{ 
     fmt.Println(b) 
     fmt.Println(s) 
     fmt.Println(string(s)) 
    } 
} 

出力:

[255 254 91 0 83 0 99 0 114 0 105 0 112 0 116 0 32 0 73 0 110 0 102 0 111 0 93 0 13 0]

SのCRIPT I NFO]


更新:

私が2つの例をテストした後、私は今、正確な問題は何かを理解しています。

ウィンドウで、行末に改行(CR + LF)を追加すると、その行にCRが読み込まれます。 readline関数がユニコードを正しく処理できないため([OD OA] = ok、[OD 00 OA 00] = not ok)

readline関数がユニコードを認識できる場合、[OD 00 OA 00]を理解し、[]バイトではなく[] uint16を返す必要があります。

だから私はutf16を読むことができないので、bufio.NewReaderを使うべきではないと思う。私はbufio.NewReader.ReadLineが読むテキストがutf8、utf16le/beであることを示すフラグとしてパラメータを受け入れることができないutf32。 goライブラリにUnicodeテキスト用のreadline関数はありますか?例えば

答えて

13

UTF16、UTF8、およびバイト順マークがUnicode Consortiumによって定義される:UTF-16 FAQUTF-8 FAQおよびByte Order Mark (BOM) FAQ。ファイルから行を読み取る


Issue 4802: bufio: reading lines is too cumbersome

ゴーでは、あまりにも面倒です。

人々はしばしば、理由はその名の をbufio.Reader.ReadLineするように描かれているが、それは(行[]バイト、isPrefixブール値、 ERRエラー)を返す、奇妙な署名を持っており、多くの作業が必要です。

ReadSliceとReadStringは常にほとんど明らかと 'N \' 見苦しい で区切りバイトを、必要とし、またライン とEOFの両方を返すことができ


Revision: f685026a2d38

bufio:新しいスキャナインターフェイス

scの新しい簡単なインターフェイスを追加(おそらくテキスト形式の)アニメーションデータ は、Scannerという新しいタイプに基づいています。それ自身の内部では のバッファリングを行います。したがって、 bufio.Readerを注入しなくても、妥当性があるはずです。入力の形式は、 "split 関数"によって定義され、デフォルトでは行に分割されます。


go1.1beta1 released

あなたはいつもの場所からバイナリとソースディストリビューションをダウンロードすることができます: https://code.google.com/p/go/downloads/list?q=go1.1beta1


ここでUTF16を変換するために、Unicodeのルールを使用するプログラムですテキストファイル行をGo UTF8でエンコードされた文字列に変換します。このコードはGo 1.1の新しいbufio.Scannerインターフェイスを利用するように改訂されました。

package main 

import (
    "bufio" 
    "bytes" 
    "encoding/binary" 
    "fmt" 
    "os" 
    "runtime" 
    "unicode/utf16" 
    "unicode/utf8" 
) 

// UTF16BytesToString converts UTF-16 encoded bytes, in big or little endian byte order, 
// to a UTF-8 encoded string. 
func UTF16BytesToString(b []byte, o binary.ByteOrder) string { 
    utf := make([]uint16, (len(b)+(2-1))/2) 
    for i := 0; i+(2-1) < len(b); i += 2 { 
     utf[i/2] = o.Uint16(b[i:]) 
    } 
    if len(b)/2 < len(utf) { 
     utf[len(utf)-1] = utf8.RuneError 
    } 
    return string(utf16.Decode(utf)) 
} 

// UTF-16 endian byte order 
const (
    unknownEndian = iota 
    bigEndian 
    littleEndian 
) 

// dropCREndian drops a terminal \r from the endian data. 
func dropCREndian(data []byte, t1, t2 byte) []byte { 
    if len(data) > 1 { 
     if data[len(data)-2] == t1 && data[len(data)-1] == t2 { 
      return data[0 : len(data)-2] 
     } 
    } 
    return data 
} 

// dropCRBE drops a terminal \r from the big endian data. 
func dropCRBE(data []byte) []byte { 
    return dropCREndian(data, '\x00', '\r') 
} 

// dropCRLE drops a terminal \r from the little endian data. 
func dropCRLE(data []byte) []byte { 
    return dropCREndian(data, '\r', '\x00') 
} 

// dropCR drops a terminal \r from the data. 
func dropCR(data []byte) ([]byte, int) { 
    var endian = unknownEndian 
    switch ld := len(data); { 
    case ld != len(dropCRLE(data)): 
     endian = littleEndian 
    case ld != len(dropCRBE(data)): 
     endian = bigEndian 
    } 
    return data, endian 
} 

// SplitFunc is a split function for a Scanner that returns each line of 
// text, stripped of any trailing end-of-line marker. The returned line may 
// be empty. The end-of-line marker is one optional carriage return followed 
// by one mandatory newline. In regular expression notation, it is `\r?\n`. 
// The last non-empty line of input will be returned even if it has no 
// newline. 
func ScanUTF16LinesFunc(byteOrder binary.ByteOrder) (bufio.SplitFunc, func() binary.ByteOrder) { 

    // Function closure variables 
    var endian = unknownEndian 
    switch byteOrder { 
    case binary.BigEndian: 
     endian = bigEndian 
    case binary.LittleEndian: 
     endian = littleEndian 
    } 
    const bom = 0xFEFF 
    var checkBOM bool = endian == unknownEndian 

    // Scanner split function 
    splitFunc := func(data []byte, atEOF bool) (advance int, token []byte, err error) { 

     if atEOF && len(data) == 0 { 
      return 0, nil, nil 
     } 

     if checkBOM { 
      checkBOM = false 
      if len(data) > 1 { 
       switch uint16(bom) { 
       case uint16(data[0])<<8 | uint16(data[1]): 
        endian = bigEndian 
        return 2, nil, nil 
       case uint16(data[1])<<8 | uint16(data[0]): 
        endian = littleEndian 
        return 2, nil, nil 
       } 
      } 
     } 

     // Scan for newline-terminated lines. 
     i := 0 
     for { 
      j := bytes.IndexByte(data[i:], '\n') 
      if j < 0 { 
       break 
      } 
      i += j 
      switch e := i % 2; e { 
      case 1: // UTF-16BE 
       if endian != littleEndian { 
        if i > 1 { 
         if data[i-1] == '\x00' { 
          endian = bigEndian 
          // We have a full newline-terminated line. 
          return i + 1, dropCRBE(data[0 : i-1]), nil 
         } 
        } 
       } 
      case 0: // UTF-16LE 
       if endian != bigEndian { 
        if i+1 < len(data) { 
         i++ 
         if data[i] == '\x00' { 
          endian = littleEndian 
          // We have a full newline-terminated line. 
          return i + 1, dropCRLE(data[0 : i-1]), nil 
         } 
        } 
       } 
      } 
      i++ 
     } 

     // If we're at EOF, we have a final, non-terminated line. Return it. 
     if atEOF { 
      // drop CR. 
      advance = len(data) 
      switch endian { 
      case bigEndian: 
       data = dropCRBE(data) 
      case littleEndian: 
       data = dropCRLE(data) 
      default: 
       data, endian = dropCR(data) 
      } 
      if endian == unknownEndian { 
       if runtime.GOOS == "windows" { 
        endian = littleEndian 
       } else { 
        endian = bigEndian 
       } 
      } 
      return advance, data, nil 
     } 

     // Request more data. 
     return 0, nil, nil 
    } 

    // Endian byte order function 
    orderFunc := func() (byteOrder binary.ByteOrder) { 
     switch endian { 
     case bigEndian: 
      byteOrder = binary.BigEndian 
     case littleEndian: 
      byteOrder = binary.LittleEndian 
     } 
     return byteOrder 
    } 

    return splitFunc, orderFunc 
} 

func main() { 
    file, err := os.Open("utf16.le.txt") 
    if err != nil { 
     fmt.Println(err) 
     os.Exit(1) 
    } 
    defer file.Close() 
    fmt.Println(file.Name()) 

    rdr := bufio.NewReader(file) 
    scanner := bufio.NewScanner(rdr) 
    var bo binary.ByteOrder // unknown, infer from data 
    // bo = binary.LittleEndian // windows 
    splitFunc, orderFunc := ScanUTF16LinesFunc(bo) 
    scanner.Split(splitFunc) 

    for scanner.Scan() { 
     b := scanner.Bytes() 
     s := UTF16BytesToString(b, orderFunc()) 
     fmt.Println(len(s), s) 
     fmt.Println(len(b), b) 
    } 
    fmt.Println(orderFunc()) 

    if err := scanner.Err(); err != nil { 
     fmt.Println(err) 
    } 
} 

出力:それはインテリジェントにBOMを解釈するunicode.BOMOverrideを、含まれているため、

utf16.le.txt 
15 "Hello, 世界" 
22 [34 0 72 0 101 0 108 0 108 0 111 0 44 0 32 0 22 78 76 117 34 0] 
0 
0 [] 
15 "Hello, 世界" 
22 [34 0 72 0 101 0 108 0 108 0 111 0 44 0 32 0 22 78 76 117 34 0] 
LittleEndian 

utf16.be.txt 
15 "Hello, 世界" 
22 [0 34 0 72 0 101 0 108 0 108 0 111 0 44 0 32 78 22 117 76 0 34] 
0 
0 [] 
15 "Hello, 世界" 
22 [0 34 0 72 0 101 0 108 0 108 0 111 0 44 0 32 78 22 117 76 0 34] 
BigEndian 
+0

今は私が変換していない問題を理解しています、それはreadlineです。だから質問が更新されます。 –

+0

問題を修正するための改訂されたプログラムです。 – peterSO

+0

あなたのプログラムをありがとう、改行には多くの標準[リンク](http://en.wikipedia.org/wiki/Newline)があるので、私はあなたのリビジョンに基づいてそれを修正します。 utf16を読むためのパッケージがないので、私は最近、この問題をGoogleに報告しなければならないと思う。現代のプログラミング言語は、特にインターネットアプリケーションにおいて、ユニコードを正しく処理できるはずです。 –

4

package main 

import (
     "errors" 
     "fmt" 
     "log" 
     "unicode/utf16" 
) 

func utf16toString(b []uint8) (string, error) { 
     if len(b)&1 != 0 { 
       return "", errors.New("len(b) must be even") 
     } 

     // Check BOM 
     var bom int 
     if len(b) >= 2 { 
       switch n := int(b[0])<<8 | int(b[1]); n { 
       case 0xfffe: 
         bom = 1 
         fallthrough 
       case 0xfeff: 
         b = b[2:] 
       } 
     } 

     w := make([]uint16, len(b)/2) 
     for i := range w { 
       w[i] = uint16(b[2*i+bom&1])<<8 | uint16(b[2*i+(bom+1)&1]) 
     } 
     return string(utf16.Decode(w)), nil 
} 

func main() { 
     // Simulated data from e.g. a file 
     b := []byte{255, 254, 91, 0, 83, 0, 99, 0, 114, 0, 105, 0, 112, 0, 116, 0, 32, 0, 73, 0, 110, 0, 102, 0, 111, 0, 93, 0, 13, 0} 
     s, err := utf16toString(b) 
     if err != nil { 
       log.Fatal(err) 
     } 

     fmt.Printf("%q", s) 
} 

(またhere

出力:


"[Script Info]\r" 
+0

また、[encoding/binary]を使用して[] uint16として読むことをお勧めします。 – cthom06

+0

@ cthom06:私はそれをお勧めしません。 – zzzz

+0

@ cthom06なぜですか? UTF16の文字は常に2バイトでエンコードされているわけではないことに注意してください(BMPの場合のみ)。 –

6

golang.org/x/text/encoding/unicodeの最新バージョンは、簡単にこれを実行することができます。

ここにはReadFileUTF16()があります。これはos.ReadFile()に似ていますが、UTF-16をデコードします。

package main 

import (
    "bytes" 
    "fmt" 
    "io/ioutil" 
    "log" 
    "strings" 

    "golang.org/x/text/encoding/unicode" 
    "golang.org/x/text/transform" 
) 

// Similar to ioutil.ReadFile() but decodes UTF-16. Useful when 
// reading data from MS-Windows systems that generate UTF-16BE files, 
// but will do the right thing if other BOMs are found. 
func ReadFileUTF16(filename string) ([]byte, error) { 

    // Read the file into a []byte: 
    raw, err := ioutil.ReadFile(filename) 
    if err != nil { 
     return nil, err 
    } 

    // Make an tranformer that converts MS-Win default to UTF8: 
    win16be := unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM) 
    // Make a transformer that is like win16be, but abides by BOM: 
    utf16bom := unicode.BOMOverride(win16be.NewDecoder()) 

    // Make a Reader that uses utf16bom: 
    unicodeReader := transform.NewReader(bytes.NewReader(raw), utf16bom) 

    // decode and print: 
    decoded, err := ioutil.ReadAll(unicodeReader) 
    return decoded, err 
} 

func main() { 
    data, err := ReadFileUTF16("inputfile.txt") 
    if err != nil { 
     log.Fatal(err) 
    } 
    final := strings.Replace(string(data), "\r\n", "\n", -1) 
    fmt.Println(final) 

} 

ここはos.Open()と似ているがスキャナを返すNewScannerUTF16です。

package main 

import (
    "bufio" 
    "fmt" 
    "log" 
    "os" 

    "golang.org/x/text/encoding/unicode" 
    "golang.org/x/text/transform" 
) 

type utfScanner interface { 
    Read(p []byte) (n int, err error) 
} 

// Creates a scanner similar to os.Open() but decodes the file as UTF-16. 
// Useful when reading data from MS-Windows systems that generate UTF-16BE 
// files, but will do the right thing if other BOMs are found. 
func NewScannerUTF16(filename string) (utfScanner, error) { 

    // Read the file into a []byte: 
    file, err := os.Open(filename) 
    if err != nil { 
     return nil, err 
    } 

    // Make an tranformer that converts MS-Win default to UTF8: 
    win16be := unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM) 
    // Make a transformer that is like win16be, but abides by BOM: 
    utf16bom := unicode.BOMOverride(win16be.NewDecoder()) 

    // Make a Reader that uses utf16bom: 
    unicodeReader := transform.NewReader(file, utf16bom) 
    return unicodeReader, nil 
} 

func main() { 

    s, err := NewScannerUTF16("inputfile.txt") 
    if err != nil { 
     log.Fatal(err) 
    } 

    scanner := bufio.NewScanner(s) 
    for scanner.Scan() { 
     fmt.Println(scanner.Text()) // Println will add back the final '\n' 
    } 
    if err := scanner.Err(); err != nil { 
     fmt.Fprintln(os.Stderr, "reading inputfile:", err) 
    } 

} 
関連する問題