セグメントを読む

(TADデータ構成である)レコードはセグメントの並びですから、前から順番に読んでいけばいいだけですね。これも説明より前にプログラムしてしまいましょう。いきなりソースが長くなりますが、やっていることは単純です。

#include <sample.h>

/* セグメントIDからその名前の対応表 */
CONST TC * SegmentIDToName(W iSegmentID)
{
  switch( iSegmentID ) {
  case TS_INFO:
    return L"管理情報セグメント"; break;
  case TS_TEXT:
    return L"文章開始セグメント"; break;
  case TS_TEXTEND:
    return L"文章終了セグメント"; break;
  case TS_FIG:
    return L"図形開始セグメント"; break;
  case TS_FIGEND:
    return L"図形終了セグメント"; break;
  case TS_IMAGE:
    return L"画像セグメント"; break;
  case TS_VOBJ:
    return L"仮身セグメント"; break;
  case TS_DFUSEN:
    return L"指定付箋セグメント"; break;
  case TS_FFUSEN:
    return L"機能付箋セグメント"; break;
  case TS_SFUSEN:
    return L"設定付箋セグメント"; break;
  case TS_TPAGE:
    return L"文章ページ割付け指定付箋"; break;
  case TS_TRULER:
    return L"行書式指定付箋"; break;
  case TS_TFONT:
    return L"文字指定付箋"; break;
  case TS_TCHAR:
    return L"特殊文字指定付箋"; break;
  case TS_TATTR:
    return L"文字割付け指定付箋"; break;
  case TS_TSTYLE:
    return L"文字修飾指定付箋"; break;
  case TS_TVAR:
    return L"変数参照指定付箋"; break;
  case TS_TMEMO:
    return L"文章メモ指定付箋"; break;
  case TS_TAPPL:
    return L"文章アプリケーション指定付箋"; break;
  case TS_FPRIM:
    return L"図形要素セグメント"; break;
  case TS_FDEF:
    return L"データ定義セグメント"; break;
  case TS_FGRP:
    return L"グループ定義セグメント"; break;
  case TS_FMAC:
    return L"マクロ定義/参照セグメント"; break;
  case TS_FATTR:
    return L"図形修飾セグメント"; break;
  case TS_FPAGE:
    return L"図形ページ割付け指定付箋"; break;
  case TS_FMEMO:
    return L"図形メモ指定付箋"; break;
  case TS_FAPPL:
    return L"図形アプリケーション指定付箋"; break;
  default:
    return L"予約";
    break;
  }
}

/* 可変長セグメントのID, 大きさ, 名前の表示 */
W ShowVariableLengthSegment(CONST UB * iRecord, W iCurrent, W iSize)
{
  W SegmentSize, LargeSegmentp;
  if ( iRecord[iCurrent + 2] == 0xff ) { /* ラージセグメントだ */
    LargeSegmentp = 1;
    SegmentSize = iRecord[iCurrent + 7] * 256 * 256 * 256 + iRecord[iCurrent + 6] * 256 * 256 + iRecord[iCurrent + 5] * 256 + iRecord[iCurrent + 4];
    printf( " large segment" );
  }
  else { /* 通常セグメントだ */
    LargeSegmentp = 0;
    SegmentSize = iRecord[iCurrent + 3] * 256 + iRecord[iCurrent + 2];
    printf( " normal segment" );
  }
  printf(", size: %3d, segment ID: %X, %S\n",
	 SegmentSize, iRecord[iCurrent], SegmentIDToName( iRecord[iCurrent] ));
  if ( LargeSegmentp == 1 ) /* セグメントのヘッダ部分の大きさだけ足す */
    iCurrent += SegmentSize + 8;
  else
    iCurrent += SegmentSize + 4;
  return iCurrent;
}

/* 固定長セグメントの中身の表示 */
W ShowFixedLengthSegment(CONST UB * iRecord, W iCurrent, W iSize)
{
  W Current = iCurrent;
  while( Current < iSize &&
	 ! ( iRecord[Current + 1] == 0xff && 0x80 <= iRecord[Current + 0] ) ) {
    TC Buffer[2] = {0}; /* 固定長セグメントの中身を一文字ずつ表示する */
    Buffer[0] = *(TC*)&iRecord[Current];
    printf("%S", Buffer); /* TRONコードの文字列表示は %S です。 */
    Current += 2; /* 一文字二バイト */
  }
  printf( "\n" );
  printf(" fixed length segment, size: %d\n", Current - iCurrent);
  return Current;
}

/* 一個のセグメントについて処理 */
W ShowSegment(CONST UB * iRecord, W iCurrent, W iSize)
{
  W NextCurrent;
  if ( iRecord[iCurrent + 1] == 0xff &&
       0x80 <= iRecord[iCurrent + 0] ) /* 可変長セグメントかな? */
    NextCurrent = ShowVariableLengthSegment(iRecord, iCurrent, iSize);
  else /* 固定長セグメントだ */
    NextCurrent = ShowFixedLengthSegment(iRecord, iCurrent, iSize);

  return NextCurrent;
}

/* レコードタイプ=1について処理 */
VOID ShowSegments(W iSize, CONST UB * iRecord)
{
  W Current = 0;
  do { /* セグメントごとに処理する */
    Current = ShowSegment(iRecord, Current, iSize);
  } while( Current < iSize );
}

/* レコードごとに処理 */
VOID ShowRecord(W iNumber, WERR iType, UH iSubtype, W iSize, CONST B * iRecord)
{
  printf("Record number: %d, type: %d, subtype: %d, size: %d\n",
	 iNumber, iType, iSubtype, iSize);
  switch( iType ) {
  case 1: /* レコードタイプ=1, つまりTAD主レコードの中身について処理する */
    ShowSegments(iSize, iRecord);
    break;
  default: /* それ以外については何もしない */
    break;
  }
}

VOID main(W argc, TC ** argv)
{
  LINK SrcLink;
  WERR WErr, SrcFD, Type;
  W SearchMode = F_FWD, Number;
  ERR Err;

  WErr = get_lnk(L"test", &SrcLink, F_NORM);
  SrcFD = opn_fil(&SrcLink, F_READ, NULL);
  while( 0 <= (Type = fnd_rec(SrcFD, SearchMode, 0xffff, 0, &Number)) ) {
    UH Subtype;
    W Size;
    B * Record;

    if ( SearchMode == F_FWD ) SearchMode = F_NFWD;
    WErr = rea_rec(SrcFD, 0, NULL, 0, &Size, &Subtype);
    Record = malloc( sizeof( B ) * Size );
    WErr = rea_rec(SrcFD, 0, Record, Size, NULL, NULL);
    ShowRecord(Number, Type, Subtype, Size, Record);
    free( Record );
  }
  Err = cls_fil( SrcFD );
}

プログラムを置いておきます

プログラムの解説

ソースコードにコメントを入れましたから、それを追えば何をやっているかは理解できると思います。流れは次のようになっています。

  1. main関数
    実身を開き、レコードを一個ずつ読み込みます。
  2. ShowRecord
    レコードタイプが1ならば中身を処理します。それ以外ならば何もしません。
  3. ShowSegments
    レコードタイプ1のレコードはセグメントが並んでいるわけですから、それぞれのセグメントごとに前から処理します。
  4. ShowSegment
    セグメントは固定長セグメントか可変長セグメントです。固定長セグメントならShowFixedLengthSegmentへ、可変長セグメントならShowVariableLengthSegmentへ処理を進めます。
  5. ShowFixedLengthSegment
    固定長セグメントには普通にTRONコードの文字列が並んでいるのでそれを表示します。
  6. ShowVariableLengthSegment
    可変長セグメントは仕様書にある通りの形をしているので、そのIDや大きさを表示します。
  7. SegmentIDToName
    可変長セグメントのIDからその名前への変換表です。

実行結果

実行するとセグメントの情報などについて表示します。左上に表示されているものが処理の対象の実身で、中身がコンソールに表示されています。ただし、いわゆるTRON第一面以外の文字は表示されないようです。ここは私が勘違いをしている可能性があります。どなたかお詳しいかたお教え願えればと思います。

[スクリーンショット]

関連する話

TADデータ構成は仕様書3.3.4 TAD データの構造定義にあるようにCFG(*1)により定義されています。つまり、単にセグメントが並んでいるだけではなく、最初に管理情報セグメントがあり、次に文章開始セグメント又は図形開始セグメントがあり…と規定されていて、それ以外の並び方は駄目、というわけです。上でのプログラムは、そのような構造を全く考えずに前から順番にセグメントを読んで表示していただけです。TADデータ構成に構造があるということで、きしもとさんがTADデータ構成を構文解析するという話を書かれています。しかし、私はこういう方法には懐疑的です。もちろん、TADの規定に則らなければなりませんし、構文解析すればデータ構造が壊れていれば解析中に発見できるでしょう。しかし、その方法では解析が最後まで終わるまで待たなければなりませんし、基本文章編集が大きい実身を読み込むと表示が遅いのは恐らくそれが原因ではないでしょうか。プログラムが正しく作られ、データが規定に則っていることを仮定して、読んだそばから処理、表示するなどすれば反応も早いのではないでしょうか。また構文解析するにしても解析が終了した部分はどんどんその後の処理部分に渡すべきです。現在の超漢字のアプリケーションはこの辺りの作りがまずいと想像でき、とても実時間処理OSには思えません。

(*1) CFG(Context Free Grammar; 文脈自由文法)の表記の仕方がBNF(Backus-Naur form)というようです。さてこの記法、何を表現しているか分かる人には分かるし分からない人には全然分からないと思います。

青空文庫変換について

さて、ここで青空文庫変換について記しておきます。青空文庫変換は、文章実身を読んでルビふりなどを行うプログラムです。プログラムの中心構造は、元の実身から新たな実身を作り、内容を複写するという簡単なものです。複写の過程でルビの指定を発見したら文字割付け指定付箋のルビ指定付箋セグメントを挿入する、という作業をしているだけです。その他色をつけたり文字を置き換えたりするのも同様の簡単な作業です。

青空文庫変換は私がTADの読み書きの方法を知るための勉強用のプログラムとして作成しました。もちろん青空文庫を読みたかったというのもありますけどね。従って、何も分からないところから仕様書を読み、プログラムを作っては試し、修正しては実行しと試行錯誤して作成しました。そのため、ソースコードを御覧になればお分かりになる通りとても汚ないものになっています。また、バグがあることは分かっていますしエラー処理もしていませんがそのままにしています。理由は、

  1. 私の勉強は済んだ
  2. 青空文庫を変換することしか能がない汎用性がないソフトウェアに完璧性を追及する気がない
  3. 動いているんだからいいじゃん

です。改良の余地はあるのですが、私の思想といいますか美学といいますか、その辺りにひっかかるためとりあえずの実用性は満たしているとしてそのままにしておきます。御要望などがあれば対応はしようとは思っています。


Tamakoshi Hiroki
Last modified: Sun Dec 1 03:37:12 JST 2002