雑記帳

ブログを使わずにArrangeNoteで作っている公開雑記帳

Sep 25, 2008 - IronPythonのエンコーディング判別処理の修正 Ver.2

「エンコーディングの処理はいろんな要因が関連するため、安直な修正を行うと、他のプログラムの箇所や他のエンコーディング形式でレベルダウン障害を引き起こします」と下の記事に書きましたが。やっぱりレベルダウンが発生していましたorz

EncodingWrapperクラスへのパッチが不十分だったため、↓のコードを実行するとエラーになってしまいます。

import sys
sys.setdefaultencoding( 'utf-8')
f=open(r'e:\temp\utf8.txt', 'w' )
f.writelines( ['あ'*40+'\n']*40 )
f.close()

↓が修正パッチのバージョン2です。EncodingWrapperに包含されている_encodingのプロパティを呼び出すようにしただけです。

*** ./IronPython/Runtime/Operations/StringOps.cs.orig	Fri Aug 22 12:05:38 2008
--- ./IronPython/Runtime/Operations/StringOps.cs	Thu Sep 25 10:07:19 2008
***************
*** 1800,1805 ****
--- 1800,1860 ----
                  res._encoding = (Encoding)_encoding.Clone();
                  return res;
              }            
+ 
+             // this method is called by StreamReader.
+             public override Decoder GetDecoder() {
+                 return _encoding.GetDecoder();
+             }
+ 
+             public override Encoder GetEncoder() {
+                 return _encoding.GetEncoder();
+             }
+ 
+             // this method is called by PythonContext.GetSourceReader().
+             public override int CodePage {
+                 get { return _encoding.CodePage; }
+             }
+ 
+             public override string BodyName {
+                 get { return _encoding.BodyName; }
+             }
+ 
+             public override string EncodingName {
+                 get { return _encoding.EncodingName; }
+             }
+ 
+             public override string HeaderName {
+                 get { return _encoding.HeaderName; }
+             }
+ 
+             public override bool IsBrowserDisplay {
+                 get { return _encoding.IsBrowserDisplay; }
+             }
+ 
+             public override bool IsBrowserSave {
+                 get { return _encoding.IsBrowserSave; }
+             }
+ 
+             public override bool IsMailNewsDisplay {
+                 get { return _encoding.IsMailNewsDisplay; }
+             }
+ 
+             public override bool IsMailNewsSave {
+                 get { return _encoding.IsMailNewsSave; }
+             }
+ 
+             public override bool IsSingleByte {
+                 get { return _encoding.IsSingleByte; }
+             }
+ 
+             public override string WebName {
+                 get { return _encoding.WebName; }
+             }
+ 
+             public override int WindowsCodePage {
+                 get { return _encoding.WindowsCodePage; }
+             }
+ 
          }
  
          private static List SplitEmptyString(bool separators) {

PythonContext.csへのパッチは変更していませんが、一応、再掲載。

*** ./IronPython/Runtime/PythonContext.cs.orig	Tue Sep 09 06:33:00 2008
--- ./IronPython/Runtime/PythonContext.cs	Wed Sep 24 01:41:00 2008
*************** namespace IronPython.Runtime {
*** 598,620 ****
                  }
              }
  
!             if (gotEncoding && sr.CurrentEncoding != PythonAsciiEncoding.Instance && encoding != sr.CurrentEncoding) {
!                 // we have both a BOM & an encoding type, throw an error
!                 throw new IOException("file has both Unicode marker and PEP-263 file encoding");
              }
  
              if (encoding == null) {
                  throw new IOException("unknown encoding type");
              }
  
!             if (!gotEncoding) {
!                 // if we didn't get an encoding seek back to the beginning...
!                 stream.Seek(startPosition, SeekOrigin.Begin);
!             } else {
!                 // if we got an encoding seek to the # of bytes we read (so the StreamReader's
!                 // buffering doesn't throw us off)
!                 stream.Seek(bytesRead, SeekOrigin.Begin);
!             }
  
              // re-read w/ the correct encoding type...
              return new SourceCodeReader(new StreamReader(stream, encoding), encoding);
--- 598,616 ----
                  }
              }
  
!             if (gotEncoding && sr.CurrentEncoding != PythonAsciiEncoding.Instance) {
!                 Encoding enc;
!                 if (!StringOps.TryGetEncoding(sr.CurrentEncoding.WebName, out enc) || enc.CodePage != encoding.CodePage) {
!                     // we have both a BOM & an encoding type, throw an error
!                     throw new IOException("file has both Unicode marker and PEP-263 file encoding");
!                 }
              }
  
              if (encoding == null) {
                  throw new IOException("unknown encoding type");
              }
  
!             stream.Seek(startPosition, SeekOrigin.Begin);
  
              // re-read w/ the correct encoding type...
              return new SourceCodeReader(new StreamReader(stream, encoding), encoding);

Sep 25, 2008 - IronPythonのエンコーディング判別処理の修正

下記のIronPython 2.0B5のエンコーディング判別処理に関連するトラブルについて色々調べていくうちに、 Src/IronPython/Runtime/Operations/StringOps.csで実装されているカスタムEncodingクラスのEncodingWrapperが実装不十分で、BOMとPEP-263 (1・2行目の# -*- coding: エンコーディング名 -*-)によるutf-8指定がエンコーディング不一致にされてしまうことが分かりました。

EncodingWrapperクラスにはもう一つ問題があって、日本語文字列を含んだutf-8形式のPythonスクリプトファイルを実行すると、ときどき日本語文字が文字化けを起こしてしまう現象を確認しています。(具体的には、ファイルの読み込みバッファサイズが0x400バイトであるため、Pythonスクリプトファイルの先頭から1024バイトごとに文字の泣き別れが発生します。)

EncodingWrapperクラスを色々調べていくうちに、StreamReaderクラスがEncodingWrapper.GetDecoder()メソッドを呼び出していることが判明しました。これを適切に実装してやることで、Pythonスクリプトファイルの日本語文字が文字化けを起こすのを回避できるようになりました。

また、BOMとPEP-263によるutf-8指定がエンコーディング不一致にされてしまう問題については、EncodingWrapperクラスのEqualsがtrueにならないので(NonStrictDecoderFallbackの異なるインスタンスが常に!Equals()となるため)、EncodingWrapper.CodePageプロパティを実装して、これを使って一致できるように修正しました。

さらに、coding:行が指定されていた場合、Visual Studio 2008のデバッガ上でPythonスクリプトのデバッグ行がずれてしまう問題も解決しました。

↓が修正パッチです。

*** ./IronPython/Runtime/Operations/StringOps.cs.orig   Fri Aug 22 12:05:00 2008
--- ./IronPython/Runtime/Operations/StringOps.cs        Wed Sep 24 23:18:50 2008
*************** namespace IronPython.Runtime.Operations
*** 1800,1805 ****
--- 1800,1820 ----
                  res._encoding = (Encoding)_encoding.Clone();
                  return res;
              }
+
+             // this method is called by StreamReader.
+             public override Decoder GetDecoder() {
+                 return _encoding.GetDecoder();
+             }
+
+             public override Encoder GetEncoder() {
+                 return _encoding.GetEncoder();
+             }
+
+             // this method is called by PythonContext.GetSourceReader().
+             public override int CodePage {
+                 get { return _encoding.CodePage; }
+             }
+
          }

          private static List SplitEmptyString(bool separators) {
*** ./IronPython/Runtime/PythonContext.cs.orig  Tue Sep 09 06:33:00 2008
--- ./IronPython/Runtime/PythonContext.cs       Wed Sep 24 01:41:00 2008
*************** namespace IronPython.Runtime {
*** 598,620 ****
                  }
              }
  
!             if (gotEncoding && sr.CurrentEncoding != PythonAsciiEncoding.Instance && encoding != sr.CurrentEncoding) {
!                 // we have both a BOM & an encoding type, throw an error
!                 throw new IOException("file has both Unicode marker and PEP-263 file encoding");
              }
  
              if (encoding == null) {
                  throw new IOException("unknown encoding type");
              }
  
!             if (!gotEncoding) {
!                 // if we didn't get an encoding seek back to the beginning...
!                 stream.Seek(startPosition, SeekOrigin.Begin);
!             } else {
!                 // if we got an encoding seek to the # of bytes we read (so the StreamReader's
!                 // buffering doesn't throw us off)
!                 stream.Seek(bytesRead, SeekOrigin.Begin);
!             }
  
              // re-read w/ the correct encoding type...
              return new SourceCodeReader(new StreamReader(stream, encoding), encoding);
--- 598,616 ----
                  }
              }
  
!             if (gotEncoding && sr.CurrentEncoding != PythonAsciiEncoding.Instance) {
!                 Encoding enc;
!                 if (!StringOps.TryGetEncoding(sr.CurrentEncoding.WebName, out enc) || enc.CodePage != encoding.CodePage) {
!                     // we have both a BOM & an encoding type, throw an error
!                     throw new IOException("file has both Unicode marker and PEP-263 file encoding");
!                 }
              }
  
              if (encoding == null) {
                  throw new IOException("unknown encoding type");
              }
  
!             stream.Seek(startPosition, SeekOrigin.Begin);
  
              // re-read w/ the correct encoding type...
              return new SourceCodeReader(new StreamReader(stream, encoding), encoding);

エンコーディングの処理はいろんな要因が関連するため、安直な修正を行うと、他のプログラムの箇所や他のエンコーディング形式でレベルダウン障害を引き起こします。本当にこの修正で十分かどうか、検証が必要です。

Sep 19, 2008 - IronPythonのエンコーディング判別処理が適切ではない

IronPython2.0B5が公開され、いよいよRC1に向けて最後の追い込みが始まったみたいなので 以前から挙動がおかしくて困っていた↓をちゃんと修正してもらおうと思って真面目に調べていたところ、 実は色々問題を抱えていることに気づきました。

# -*- coding: utf-8 -*-

IronPython2.0B5 の Src/IronPython/Runtime/PythonContext.cs をデバッグしてみて分かったことですけど、スクリプトにエンコーディングを指定した場合のPythonContext.GetSourceReaderの動作が原因で、以下の問題が生じます。

  1. BOMが付いたUTF-8のスクリプトの場合、Tokenizer.TryGetEncodingの返すエンコーディングが独自のStringOps.EncodingWrapperになってしまい、sr.CurrentEncodingのUTF8Encodingと一致しなくなって"file has both Unicode marker and PEP-263 file encoding"エラーになる
  2. BOMが付いたUTF-16のスクリプトの場合、stream.Seek(bytesRead, SeekOrigin.Begin)のbytesReadの値が実はchar単位の数値になっているため、シークする位置が正しくない (PythonContext.ReadOneLineでsr.Readの戻り値をbytesRead変数に入れていることがおかしい)
  3. BOMのないUTF-16スクリプトはsr.CurrentEncodingがPythonAsciiEncodingなので coding: を判別できない
  4. 上記以外の場合、coding: 行をstream.Seek(bytesRead, SeekOrigin.Begin)でスキップしてしまうため、スクリプトのParserは1行目または2行目を知らないでスクリプトを読み込んでしまい、PythonスクリプトをVisual Studioのデバッガでデバッグすると実際の停止位置と異なる行がハイライト表示になる(黄色く染まる)

C Python2.5のチュートリアル「2.2.3 Source Code Encoding」によるとUTF-8のBOMは識別するそうなので、IronPythonでもUTF-8のBOMのサポートは必要です。UTF-16のスクリプトはBOMの有無に関わらずC Python2.5.2でも動きませんから、無視してよさそうです。

1.と4.のパッチを真面目に考えることにしましょう。 そんな時間があればの話ですが。

Sep 09, 2008 - NHK趣味悠々見ました。

HDDレコーダで地デジ録画した番組を見ました。

波紋の描画が速すぎて美しくない orz

波紋の滑らかさの数値を大きくすれば、もう少し見れたものになると思います。

拙作ソフトがテレビに放送されることなんて、貴重な経験ができました。 もうちょっと、人前に出しても恥ずかしくないものを作るように心がけようという気になりました。

Sep 09, 2008 - NHK趣味悠々 中高年のためのパソコンシリーズ パソコンソフト活用術入門 NHK教育20:00~20:25

さあHDDレコーダーの準備はできました。

後は放送を待つだけです。楽しみ(^^)

記事の一覧

Copyright © 2008 Koichi Yamamoto