ラベル program の投稿を表示しています。 すべての投稿を表示
ラベル program の投稿を表示しています。 すべての投稿を表示

2010年4月10日土曜日

Word文書のチェックツール2: 指定以外のフォントが使われている場所を見つける

以前「Word文書のチェックツールを作ってみました→障害だらけ。。。」という記事でWord文書の中で指定されたフォント以外を使っている箇所を自動的に探すことをツールで自動化してみる試みについて書きました。

その中で、Range.MoveにWdUnits.CharacterFormattingを渡したときの挙動が不審だと言いましたが、やっと理解することができました。これは文字スタイルが変更されている箇所まで移動なんですね。そう言われると当たり前な気がしますが、ポイントは段落が変わっても、文字スタイルが「段落フォント」である限り、そこでは止まらないということです。

例えば、2つの段落に「見出し1」「本文」という段落スタイルが適用されていて、見出し1のフォント設定が「創英角ゴシックUB」、本文のフォント設定が「MS Pゴシック」だとしても、その間はCharacterFormattingは同じであると判断されます。うーん。便利なような不便なような。。。いや不便ですね :s

私の中でのイメージでは、段落スタイルと文字スタイルは親子関係にあって、1段落の中に複数の文字スタイルがあり、段落が切れると文字スタイルも一旦切れるものだと思っていました。

でも実際は違うんですね。段落スタイルと文字スタイルは完全に別物なんですね。文字スタイルと段落スタイルの片方だけ追っていても、フォントの変更は検出できないんですね。なんてことだ。。。:(

あと、このフォント一覧検出プロジェクト(!?)で試行錯誤してて思いましたが、マクロでの検索(Find)や移動(Move)って驚くほど遅いですね。びっくりします。

ただ文書を走査するだけなら、XML(WordML)に書き出してからDOM使った方が早いに決まっているんですけどね。それだと文書上の何ページの何行何列か分からないので、こんな手法を取らざるを得ないのです。

痛し痒し。。。

2009年4月6日月曜日

Word文書のチェックツールを作ってみました→障害だらけ。。。

仕事で作成しているWordの文書は日本語は「MS Pゴシック」英数字は「Arial」じゃなきゃダメという書式(フォント)の決まりがあります。そのため、提出前にちゃんと指定されているフォントで書かれているか、ディスプレイをにらみながらチェックします。文字にアンチエイリアシング(or Clear Type)を使っていればパッと見てわかるんですが、そうでない人(何でそうしない??)は、300%ぐらいに拡大してにらみつけています。

まーこんなのちゃんとWordのスタイル機能使って作成していれば、チェックするまでもないんですが、たまにどこぞからコピーしてきた個所があると、コピー元のダメダメな書式が反映されてしまい、検閲に引っ掛かります。

さて、こんな作業に労力も掛けていられないので自動化してしまおうと企みました。VBAでちょちょいとできそうなので。私の場合はVBAではなくC# + COMですが。

要は文書内の「文字スタイル」を頭から走査して、そのフォントを確認すればいいわけです。何の複雑さもないですよねー :)

Word文書の操作を学ぶ

ヘルプを見ると、Word文書(Documentオブジェクト)に対する操作の多くは、操作範囲を表すRangeオブジェクトに対して行うと書いてあります。Excelと同じですね。また、そのRangeを移動するためのNextメソッドというのがあることも分かりました(他にも移動する手立てはたくさんあります)。これはExcelにはないWord独自のものですね。Nextメソッドの引数には、どういう単位で移動するかを表すWdUnits型の引数があります。例えば行単位ならWdUnits.wdRow、段落単位ならWdUnits.wdParagraphといった具合です。具体的なコードは以下の通り。

object wdUnit = WdUnits.wdParagraph;
object value = 1;
while ((range = range.Next(ref wdUnit, ref value)) != null)
{
  ErrorList.Items.Add(range.Text);
}

さらにヘルプを見ると(実際にはVisual StudioのInteliSenseが教えてくれたんですが)、WdUnits.wdCharacterFormattingという値があるじゃないですか。これが文字書式単位で移動するというパラメータのようです。もうこれで8割ぐらいできたも同然です :)

8割できたはずが、大きな壁

ところがどうにもうまく動きません!:o 上記のソースでいえば、range.Textで取ってきた値の一覧を見ると、文字書式毎の一覧になりません。というか、そもそも欠落だらけで、何単位で取ってきたのかもよく分かりません! wdCharacterFormattingがうまく動かない(=バグ)なのかと思い、試しにwdRowを指定してみると、実行時に「引数が違います」と怒られてしまいます。もうわけが分からない!!

WordのVBAに関する情報は少なく、この状況を打開できる術がありません。。純正ヘルプとMSDN以外では、日本語の情報はほとんどありません。

困った困った。。。

2009年1月1日木曜日

[WireWhale]激早!! FileStreamのPosition、Length→自前で保持

今までキャプチャファイルの読み込みはFileStreamからPositionプロパティを使ってせっせと読み出し、Lengthプロパティの位置まで来たら止める、という方法をとってきました。これだと50MBのキャプチャファイルを読み出すのに大体10秒ぐらいかかっていました。

Wireshark(というかcapinfo.exe)でキャプチャ情報を読み出すのも大体それぐらいかかっているので、それが普通かなと思っていました。が1ファイル開くのに10秒も待っていられない! 何とかならないものかと試行錯誤していました。

そんな中、CodeProjectにFast Binary File Reading with C#という、ストリームの読み込みの手法毎の性能比較の記事があったの でWireWhaleもチューニングしてみました。具体的には、PositionLengthプロパティの使用を極力減らし、値は変数で保持、Positionを移動するときはSeekを使うようにしてみました。

結果、、、びっくり!!! 何と50MBのキャプチャを開くのに5秒かからなくなりました!!! プロパティ内で何してるんでしょうね!?

やっぱりこういう細かい調整が性能を大きく左右するんですね。とても参考になりました。

参考
Fast Binary File Reading with C#

2008年12月7日日曜日

Excelファイルがデファクトであることの障害: プログラムとの親和性の低さ

会社で使っているデータの多くがExcelファイル。これらをプログラム(PHP, Perl等)から利用できれば、作業の多くを自動化できるのにな~と思っています。

「思っています」というのは、色々考えてみましたが、有効な方法が思い当たらないからです。

一番一般的なのはCOMを使ってVBAを外部から使う方法ですが、これはWindowsでしか使えません。そのため、私のPCでは動かすことができますが、みんなに使ってもらうためにサーバに配置する、というようなことはできません(みんなが使えるWindowsサーバがないため)。

PHPやPerlにはそれぞれExcelファイルを操作するためのライブラリが用意されていますが、Excelファイルの作成こそある程度は可能ですが、読み込みは完璧ではないようです。サーバ/クライアントシステムとして、サーバ上でデータ操作をして、最終的な出力をExcelファイルとしてダウンロードさせる、といった使い方はできそうですが、サーバ上のExcelファイルを検索して、最終的な出力をHTMLで行う、という用途には向かないようです

やりたいことはまさに上記の後者の例で、サーバ上にある試験項目表のデータ(100ファイルぐらい)を検索した結果をHTMLで表示する、ということなのです。以前Pukiwikiに検索機能を付けた時は、Pukiwikiの全エントリを全文検索して結果を表示させる、という手法をとり、2,000弱のエントリで問題なく動いるのですが、Excelを100ファイルとなると、ファイルから動的にデータを読み出して処理する、というのは難しそうです。

動的なデータ読み出しができないのなら、静的にデータベースを作るなり、XMLに分解するなりすればいいのですが、ファイルが更新されるたびにデータを作り直さなければならないという問題が出てきます。1日1回だけに制限するにしても、更新するのは結局私なんだろうな、と思うとやっぱり現実的ではない気がしてきます。

Excelファイルでデータを管理すること自体、決して悪いことではないと思うのですが、自動化させる、という点からみると何とも忌々しいものです。

2008年11月30日日曜日

FileStreamのPositionがなぜかずれる

私は開発には .NET Framework 3.5 SP1 を Visual C# 2008 SP1 で使っていますが、その環境のせいか、何のせいか、FileStreamPositionを指定しても、反映されない場合がたまにあります。これが一度出るとはまってしまう。。。そして何が原因か分からないまま、事象は解決する。。。

デバッガでPositionを変更した直後に止めてみて値を見ると、代入している値は確かに目的の値なのに、代入直後にStreamPosition0x0000001cになる。つまり以下のコードだけでアウト

  // set source stream position
  source.Position = 0x5000;

 // read packet ↓ここでデバッガを使ってPositionを見ると0x0000001cになっている
 FramePacket lastPacket = new FramePacket(source);

ちなみにseekを使っても同じ。なぜか入れた値のままにならない。デバッガで値を無理やり書き換えてもすぐに0x0000001cに戻る。なんだこりゃ。。。

メガバイトを超えるファイルを扱う場合、これがうまく動かない場合は致命傷です。試行錯誤を繰り返すと治るのですが、原因がよくわからないのは何とも気持ちが悪い。なんでしょうね。

2008年11月23日日曜日

キャプチャファイルのキャプチャ終了時間を推測

Wireshark等のパケットキャプチャで収集したキャプチャファイルはただ単に取得したパケットを数珠つなぎにして保存しているため、収集を終了した時間がいつか、というのはファイルを最後まで走査してみないとわからないようです(収集開始時間は1パケット目の到着時間がそれにあたるので容易に判定できますが)。そのため、収集終了時間を知るためだけにファイル全体を走査するという面倒なことをしなければなりません。これはちょっと嫌です。というかだいぶ嫌です。ファイルサイズが500Mあったら。。。とか考えたくないですね。

要はファイル内の最後のパケットを見つけられればいい、ということで、WireWhaleではファイルの末尾から以下のバイト列を見つけることで最終パケットを推測しています。

  AA -- -- -- 00 -- -- 00 00 BB BB 00 00

  --   : 任意のバイト
  AA: 1パケット目のEpoc時間の上位1バイト
  BB BB: ファイルの末尾からのバイト数

上記はパケットのFRAMEヘッダ部の文字列です。Aは1パケット目のEpoc時間の上位1バイトで、これが1パケット目と同じということは、1パケット目を収集してから16,777,216秒(約4,660時間)経過していない、というのと同意になります。じゃあ4660時間以上のキャプチャファイルの場合はどうするんだということになりますが、そこは保証外ですよ。無茶言わないでください。

他にも細かいロジックを色々入れればもっと精度の高い推測ができるかもしれませんが、当面はこれでいきます。