57171


＜やりたいこと＞  
ある通信データをstr型で引き渡すようになっています。  
str型変数の中身のエンコードを判別して、正しく日本語表示および改行を整えて、表示したいです。実際の出力先はtk.Textですが、printでの出力でも同じことなので、ひとまずprintで正しく表示したいと考えています。

＜問題点＞  
通信データは、日本語文字列をUTF-8でエンコードしているため、str型でUTF-8のエンコード文字列が渡されます。  
具体的には、

[code]

    >>>t = 'こんにちは'
    >>>bt8 = t.encode('utf-8')
    >>>bt8
    b'\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81\xa1\xe3\x81\xaf'
    とエンコードされますが、
    >>>data = '\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81\xa1\xe3\x81\xaf'
    このように本来bytes型で扱いたいコードがstr型となっています。
    
[/code]

１．文字列をUTF-8だと判断する。今回はUTF-8ですが、S-JISなどの可能性もあり得ます。  
２．str型→bytes型へ文字列の表現のまま変換する。  
３．bytes型を①の判別した結果から適切なdecodeで文字列に戻す。  
こういった手順になると思います。あるいは２，１が逆かもしれませんが。  
解決方法が分からず困っています。

ご存知の方がいらっしゃいましたら、ご教示をお願いいたします。


CC BY-SA 4.0
2019-07-29T03:09:57.717
57001
2019-08-05T02:10:22.953
2019-07-29T05:08:51.623
19110
32891
question
python python3
Pyhon str型のデータの文字コードを判別して、改行コードなどを正しく表示したい。


以下あたりの記事から関数をつくってみました。  
簡単なテストしかしていないので、バグ等あると思います。  
本当に使うならいろんなパターンで試して、改良等もやっておいてください。

[Process escape sequences in a string in
Python](https://stackoverflow.com/q/4020539/9014308) /
[参考にした回答](https://stackoverflow.com/a/24519338/9014308)  
[how do I .decode('string-escape') in
Python3?](https://stackoverflow.com/q/14820429/9014308) /
[参考にした回答](https://stackoverflow.com/a/15528611/9014308)

質問の`2.`の部分を以下に作成しました。  
元のデータは`str`と`bytes`のどちらでも使えます。  
ASCIIの範囲(～0x7F)を超えた文字やコードポイントがあった場合の処理を`encoding`,`errors`パラメータで指定可能にしています。  
複数の`encoding`が混在するようなデータは扱えません。

[code]

    import re
    
    UNESCAPE_ENCODE_RE = re.compile(b'''
      \\\\( U[0-9A-Fa-f]{8} # 8-digit hex escapes
          | u[0-9A-Fa-f]{4} # 4-digit hex escapes
          | x[0-9A-Fa-f]{2} # 2-digit hex escapes
          | [0-7]{1,3}   # Octal escapes
          | N\{[^}]+\}   # Unicode characters by name
          | \\\\         # Backslash escape
          | [\'"abfnrtv] # Single-character escapes
          | .   # Other undefined character escapes
          | $   # ?? bogus escape (end of line) ??
          )''', re.VERBOSE)
    
    def unescape_encode(data, encoding='UTF-8', errors='backslashreplace'):
    
      def unescape(m):
    
        b = m.group(1)
    
        if len(b) == 0:
          raise ValueError("Invalid character escape: '\\'.")
        i = b[0]
        if i == 85 or i == 117: # U00000000-Uffffffff/UFFFFFFFF, u0000-uffff/uFFFF
          return chr(int(b[1:], 16)).encode(encoding, errors)
        elif i == 120:      # x00-xff/xFF
          v = int(b[1:], 16)
        elif 48 <= i <= 55: # 0-777
          v = int(b, 8)
        elif i == 78:       # N{.*} Unicode characters by name
          return (b'\\' + b).decode('unicode-escape').encode(encoding, errors)
        elif i == 34: return b'"'
        elif i == 39: return b"'"
        elif i == 92: return b'\\'
        elif i == 97: return b'\a'
        elif i == 98: return b'\b'
        elif i == 102: return b'\f'
        elif i == 110: return b'\n'
        elif i == 114: return b'\r'
        elif i == 116: return b'\t'
        elif i == 118: return b'\v'
        else:
          s = b.decode('ascii')
          raise UnicodeDecodeError(
            'stringescape', data, m.start(), m.end(), "Invalid escape: %r" % s
          )
    
        return bytes((v, ))
    
      if isinstance(data, bytes):
        workbytes = data
      elif isinstance(data, str):
        workbytes = bytes(data, encoding, errors)
      else:
        raise TypeError
    
      return UNESCAPE_ENCODE_RE.sub(unescape, workbytes)
    
[/code]

で、コメントにも書いた記事を元に`1.`と`3.`の処理を以下の様に行います。  
[Python3.3.1でcChardetとpython3-chardetを使ったメモ](https://qiita.com/katryo/items/0e3b1b2c784a4a955927)

[code]

    import cchardet
    
    f = open('EscapedSjis.txt') # 何か適当にエスケープ化したテキストをファイルにしておきます。
    data = f.read()
    f.close()
    
    # 上記の代りに data = '\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81\xa1\xe3\x81\xaf' でも良い
    
    unescapedbytes = unescape_encode(data)     # 2. str/bytesのエスケープ表現を解除して文字コード化
    detected = cchardet.detect(unescapedbytes) # 1. bytesデータを解析してencodingを判定
    print(detected)
    
    if detected['confidence'] > 0.8: # 確度情報を元に処理を変えてみる
      print(unescapedbytes.decode(detected['encoding'])) # 3. 検出したencodingでdecode
    else:
      print(unescapedbytes.decode(detected['encoding'], 'replace')) # encoding不能な文字は?に置換
    
[/code]


CC BY-SA 4.0
2019-08-05T02:10:22.953
57171
2019-08-05T02:10:22.953
26370
57001
answer