C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[9]: UTF-8の全角「テ」だけ変換できない


(過去ログ 171 を表示中)

[トピック内 15 記事 (1 - 15 表示)]  << 0 >>

■98526 / inTopicNo.1)  UTF-8の全角「テ」だけ変換できない
  
□投稿者/ tosh (7回)-(2021/11/29(Mon) 15:49:40)

分類:[C#] 

お世話になります。

Windows10のメモ帳のデフォルト文字コードがUTF-8になったので
iniファイルの読み込み時文字化けを避けるため、UTF-8からSJISにエンコードする処理を
入れたのですが、特定の文字だけ文字化けが解消しない状態です。

具体的にはUTF-8で書かれたiniファイルから読み込んだ文字列に
全角カタカナの「テ」が含まれる場合、「テ」そのものと次の文字だけが化けた状態になります。
変換処理のコードは以下の通りです。

//iniファイルから文字列を読み込む
var sb = new StringBuilder(1024);
GetPrivateProfileString(section, key, "", sb, Convert.ToUInt32(sb.Capacity), filePath);
string str = sb.ToString();

//バイト配列に変換する
byte[] bytes = System.Text.Encoding.Default.GetBytes(str);

//バイト配列をSJISの文字コードとしてStringに変換する
str = System.Text.Encoding.UTF8.GetString(bytes);

UTF-8の文字列「システム」を上記処理で変換した場合
strの内容は「シスチE&#65533;&#65533;」になります。
(↑掲示板に投稿する際に更に化けましたが、「&#65533;」は黒い菱形みたいな記号が2つです。)
文字の順番を変えたり別の語句で試しても「テ」の部分で同じように化けるため
「テ」という文字そのものが問題の様です。

バイト配列bytesの内容は以下の通りです。
[0]  227 = e3   e382b7  シ
[1]  130 = 82
[2]  183 = b7
[3]  227 = e3   e382b9  ス
[4]  130 = 82
[5]  185 = b9
[6]  227 = e3   e38381  該当なし(本来はe38286)
[7]  131 = 83
[8]  129 = 81
[9]   69 = 45   4583a0  該当なし(本来はe383a0)
[10] 131 = 83
[11] 160 = a0

「シ」と「ス」迄は正しく変換されていますが
「テ」と「ム」はUTF-8に存在しないコードに変わっています。
順番を変えても「テ」だけは化けたままです。

不可解な現象なので、変換時の処理が間違っているのではと思うのですが
正しい方法が分かりません。
もしくはWindowsやVisual Studioのバグ等でしょうか。
ご存じの方おられましたら、よろしくお願いいたします。

OS:Windows10 64bit
開発環境:Visual Studio 2017

引用返信 編集キー/
■98527 / inTopicNo.2)  Re[1]: UTF-8の全角「テ」だけ変換できない
□投稿者/ WebSurfer (2383回)-(2021/11/29(Mon) 16:29:30)
No98526 (tosh さん) に返信

> //バイト配列に変換する
> byte[] bytes = System.Text.Encoding.Default.GetBytes(str);

それは str という文字列を Shift_JIS のバイト列に変換していて、

> //バイト配列をSJISの文字コードとしてStringに変換する
> str = System.Text.Encoding.UTF8.GetString(bytes);

その Shift_JIS のバイト列を UTF-8 として文字列に戻しているようですけど?
引用返信 編集キー/
■98528 / inTopicNo.3)  Re[1]: UTF-8の全角「テ」だけ変換できない
□投稿者/ KOZ (176回)-(2021/11/29(Mon) 16:33:01)
No98526 (tosh さん) に返信
> 具体的にはUTF-8で書かれたiniファイルから読み込んだ文字列に
> 全角カタカナの「テ」が含まれる場合、「テ」そのものと次の文字だけが化けた状態になります。
> 変換処理のコードは以下の通りです。
>
> //iniファイルから文字列を読み込む
> var sb = new StringBuilder(1024);
> GetPrivateProfileString(section, key, "", sb, Convert.ToUInt32(sb.Capacity), filePath);

GetPrivateProfileString って独自のメソッドですか?
API を使ってるなら、この時点で文字化けしてるのでは?

引用返信 編集キー/
■98529 / inTopicNo.4)  Re[1]: UTF-8の全角「テ」だけ変換できない
□投稿者/ 魔界の仮面弁士 (3229回)-(2021/11/29(Mon) 16:37:04)
No98526 (tosh さん) に返信
> Windows10のメモ帳のデフォルト文字コードがUTF-8になったので
> iniファイルの読み込み時文字化けを避けるため、UTF-8からSJISにエンコードする処理を

ini ファイルは UTF-8 には対応していませんが、
UTF-16 であれば、25 年以上前から対応していますね。

現行のメモ帳なら「UTF-16LE」、少し古いメモ帳なら「Unicode」を
文字コードとして選んで保存されていた場合には、標準 API である
 GetPrivateProfileStringW
 WritePrivateProfileStringW
等で読み書き可能です。
独自実装で読み取っていたり、Ansi バージョンを使っていた場合は別として。


> var sb = new StringBuilder(1024);
> GetPrivateProfileString(section, key, "", sb, Convert.ToUInt32(sb.Capacity), filePath);

DllImport 属性の指定はどうなっていますか?

少なくとも GetPrivateProfileStringA や GetPrivateProfileStringW は
UTF-8 でエンコードされたファイルをデコードすることはできないので、
文字種によっては破損します。


> //バイト配列に変換する
> byte[] bytes = System.Text.Encoding.Default.GetBytes(str);
この段階では対処が遅すぎます。

フランス語でタイピングされた手紙を英語専用の OCR で読み取って、
英語辞書に未登録の単語として読み取られた部分だけを、
仏英時点で追加変換しているようなもの。


文字コードを変換するのであれば、ini ファイルのバイナリを File.ReadAllBytes で読み取ってから
 案1) System.Text.Encoding.Unicode で再保存してから、Unicode 版の GetPrivateProfileStringW で読む
 案2) System.Text.Encoding.Default で再保存してから、ANSI 版の GetPrivateProfileStringA で読む
という手順をとってください。(もしくは、元のテキストを全部自前で読み取るような実装を作りこむ)

いっそのこと、ini ファイル自体を廃止して、xml や json に移行するという選択肢もありますが、
今から移行するにはコストが高すぎるかな。
引用返信 編集キー/
■98530 / inTopicNo.5)  Re[2]: UTF-8の全角「テ」だけ変換できない
□投稿者/ tosh (8回)-(2021/11/29(Mon) 16:43:30)
KOZ様

GetPrivateProfileStringはDLLなのですが、
UTF-8を読み込んで問題ないのかどうかは正直わかりません。
別の方法も考えてみます。

[DllImport("KERNEL32.DLL")]
public static extern uint GetPrivateProfileString(
       string lpAppName,
       string lpKeyName,
       string lpDefault,
       StringBuilder lpReturnedString,
       uint nSize,
       string lpFileName);

引用返信 編集キー/
■98531 / inTopicNo.6)  Re[3]: UTF-8の全角「テ」だけ変換できない
□投稿者/ WebSurfer (2384回)-(2021/11/29(Mon) 16:57:59)
以下の記事が参考になるかも

iniファイル読み込みと文字コード
http://tech.hikware.com/article/20170817a.html
引用返信 編集キー/
■98532 / inTopicNo.7)  Re[2]: UTF-8の全角「テ」だけ変換できない
□投稿者/ 魔界の仮面弁士 (3230回)-(2021/11/29(Mon) 17:18:50)
No98529 (魔界の仮面弁士) に追記
> ini ファイルのバイナリを File.ReadAllBytes で読み取ってから
> 案1) System.Text.Encoding.Unicode で再保存してから、Unicode 版の GetPrivateProfileStringW で読む
> 案2) System.Text.Encoding.Default で再保存してから、ANSI 版の GetPrivateProfileStringA で読む

読み取ったバイナリを、適切な Encoding (この場合は UTF8 ) で文字列化してから
その文字列を Unicode または Default 指定で保存しなおす、ということです。

あるいは ReadAllBytes ではなく
 File.ReadAllText(filePath, そのファイルのEncoding)
で読み取った文字列を
 File.WriteAllText( filePath, 読み取った文字列, UnicodeまたはDefault )
で保存する形でも OK です。こっちの方が手っ取り早いかな。


> という手順をとってください。(もしくは、元のテキストを全部自前で読み取るような実装を作りこむ)

UTF-8 実装版を見つけたので紹介しておきます。

【.NET】UTF-8対応Iniファイルの読み込みと書き込み
https://qiita.com/yaju/items/4fa6c5f52c12b4943426
引用返信 編集キー/
■98533 / inTopicNo.8)  Re[4]: UTF-8の全角「テ」だけ変換できない
□投稿者/ tosh (10回)-(2021/11/29(Mon) 17:21:42)
魔界の仮面弁士様

分かりやすい回答ありがとうございます。
バイナリで読み込んでから一旦ANSIで保存し直す方法を試してみましたが
下記の処理では文字化け状態で保存されてしまいました。
保存方法及びSystem.Text.Encoding.Default で変換するタイミングが掴めない状態です。

// ファイルの内容をバイト配列にすべて読み込む
byte[] bs = System.IO.File.ReadAllBytes(filePath);

// ファイルを開く
StreamWriter writer = new StreamWriter(tmpFilePath, false, System.Text.Encoding.Default);

// テキストを書き込む
writer.Write(System.Text.Encoding.Default.GetString(bs));


引用返信 編集キー/
■98534 / inTopicNo.9)  Re[5]: UTF-8の全角「テ」だけ変換できない
□投稿者/ tosh (11回)-(2021/11/29(Mon) 17:30:41)
最後の行は
writer.Write(System.Text.Encoding.UTF8.GetString(bs));
でした。申し訳ないです。

引用返信 編集キー/
■98535 / inTopicNo.10)  Re[5]: UTF-8の全角「テ」だけ変換できない
□投稿者/ WebSurfer (2385回)-(2021/11/29(Mon) 17:31:20)
No98533 (tosh さん) に返信

> 保存方法及びSystem.Text.Encoding.Default で変換するタイミングが掴めない状態です。

System.Text.Encoding.Default は日本語 OS だと Shift_JIS だということが分かってない?


引用返信 編集キー/
■98536 / inTopicNo.11)  Re[6]: UTF-8の全角「テ」だけ変換できない
□投稿者/ tosh (12回)-(2021/11/29(Mon) 17:34:09)
下記処理でtmpファイルに一時保存した後
GetPrivateProfileStringAを使って読み込む方法で
目的通りの動作が実現できました。

// ファイルの内容をバイト配列にすべて読み込む
byte[] bs = System.IO.File.ReadAllBytes(filePath);

// ファイルを開く
StreamWriter writer = new StreamWriter(tmpFilePath, false, System.Text.Encoding.Default);

// テキストを書き込む
writer.Write(System.Text.Encoding.UTF8.GetString(bs));

ありがとうございました!


解決済み
引用返信 編集キー/
■98538 / inTopicNo.12)  Re[7]: UTF-8の全角「テ」だけ変換できない
□投稿者/ 魔界の仮面弁士 (3232回)-(2021/11/29(Mon) 18:13:25)
No98536 (tosh さん) に返信
> 下記処理でtmpファイルに一時保存した後
> GetPrivateProfileStringAを使って読み込む方法で

UTF-8 な ini ファイルを GetPrivateProfileStringA で読み取るのは NG
UTF-8 な ini ファイルを GetPrivateProfileStringW で読み取るのは NG

UTF-16LE な ini ファイルを GetPrivateProfileStringA で読み取るのは OK
UTF-16LE な ini ファイルを GetPrivateProfileStringW で読み取るのは OK
Shift_JIS な ini ファイルを GetPrivateProfileStringL で読み取るのは OK
Shift_JIS な ini ファイルを GetPrivateProfileStringW で読み取るのは OK

とはいえ、元が UTF-8 なファイルなのであれば、折角なので
BOM 付き UTF-16 (リトルエンディアン) で保存して、W 系 API を使うべきだと思いますよ。

「Shift_JIS の文字集合に無い文字」が UTF-8 の ini ファイルに含まれていた場合に備えて。

[DllImport("KERNEL32.DLL", CharSet = CharSet.Auto)]
public static extern uint GetPrivateProfileString(…


> 目的通りの動作が実現できました。

StreamWriter を using ブロックで囲むか、または Close メソッドを呼び出しましょう。
あるいは StreamWriter ではなく File.WriteAllText で出力するとか。

File.WriteAllText(tmpFilePath, File.ReadAllText(filePath, Encoding.UTF8), Encoding.Unicode);
GetPrivateProfileString(section, key, "", sb, Convert.ToUInt32(sb.Capacity), tmpFilePath);
解決済み
引用返信 編集キー/
■98544 / inTopicNo.13)  Re[7]: UTF-8の全角「テ」だけ変換できない
□投稿者/ KOZ (177回)-(2021/11/30(Tue) 09:38:46)
No98536 (tosh さん) に返信
> // ファイルの内容をバイト配列にすべて読み込む
> byte[] bs = System.IO.File.ReadAllBytes(filePath);
>
> // ファイルを開く
> StreamWriter writer = new StreamWriter(tmpFilePath, false, System.Text.Encoding.Default);
>
> // テキストを書き込む
> writer.Write(System.Text.Encoding.UTF8.GetString(bs));
>

気になってやってみたんですが、BOM の部分ごと変換されるので、"?"が先頭につきますね。
先頭に改行が入っていればいいですが、セクション名になっていると、そのセクションが読めないです。

string buffer = System.IO.File.ReadAllText(filePath, Encoding.UTF8);
System.IO.File.WriteAllText(tmpFilePath, buffer, Encoding.Unicode);

こうしたほうが良いと思います。

解決済み
引用返信 編集キー/
■98546 / inTopicNo.14)  Re[8]: UTF-8の全角「テ」だけ変換できない
□投稿者/ 魔界の仮面弁士 (3233回)-(2021/11/30(Tue) 10:19:10)
No98544 (KOZ さん) に返信
> 気になってやってみたんですが、BOM の部分ごと変換されるので、"?"が先頭につきますね。

メモ帳で新規作成した UTF-8 ファイルは、既定では BOM 無しで生成されるので、
今回のケースでは大丈夫な気もします。
https://forest.watch.impress.co.jp/docs/news/1157696.html

もちろん、「意図的に UTF-8 with BOM で保存した」とか、「メモ帳以外で作成された場合」は
UTF-8 の BOM が混入する可能性がありえるわけですが…その場合はそもそも、
UTF-8 以外(Shift_JIS や UTF-16 など)で保存されている可能性もあるわけで。


一方、「メモ帳での新規作成」ではなく「既存ファイル」の操作を目的とするのであれば、
ini ファイルを始めから Unicode (BOM 付 UTF-16) で保存しておくことをお奨めします。

これなら新旧いずれのメモ帳で開いたとしても、別の文字コードに変わってしまうことは無いですし、
プログラムで読み込む際にも、わざわざ変換せずに直接 GetPrivateProfileString できます。


> こうしたほうが良いと思います。
No98538
>> File.WriteAllText(tmpFilePath, File.ReadAllText(filePath, Encoding.UTF8), Encoding.Unicode);
と同じかも?
解決済み
引用返信 編集キー/
■98548 / inTopicNo.15)  Re[9]: UTF-8の全角「テ」だけ変換できない
□投稿者/ KOZ (178回)-(2021/11/30(Tue) 12:02:15)
No98546 (魔界の仮面弁士 さん) に返信
> メモ帳で新規作成した UTF-8 ファイルは、既定では BOM 無しで生成されるので、
> 今回のケースでは大丈夫な気もします。
> https://forest.watch.impress.co.jp/docs/news/1157696.html

あー、通常の Windows 10 だと UTF-8 と UTF-8(BOM付) が選択できるんですね。
LTSC だと、ANSI/Unicode/Unicode big endian/UTF-8 しかないので気づきませんでした。

ini ファイルを自動判定して読み書きするクラスを作ろうかと思ったけどちょっと考えないと・・・
いや、ini ファイルからの脱却のが先か。

> No98538
> >> File.WriteAllText(tmpFilePath, File.ReadAllText(filePath, Encoding.UTF8), Encoding.Unicode);
> と同じかも?
たし蟹。(^_^;)
解決済み
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -