未知のCDデータの表示 - リッチテキスト - 「NotesPeek」をQtでリメイク

ちょっと前の記事「伝説のツール「NotesPeek」をQtでリメイク開発記2017-7-23 - Chiburu Systemsのブログ」で、リッチテキストについて着手したことをご報告しました。この中で、リッチテキストは多種多様な「CDxxxx」という複合データ(Composite Date)によって構成されているとお伝えしました。

ここで簡単にまとめてみます。

  • リッチテキストはCDデータの連続体である。
  • CDデータは9.0.1において150種類以上ある。
  • 1つのCDデータはヘッダー情報とコンテンツ情報でできている。
  • ヘッダー情報はサイズによってBSIG、WSIG、LSIGの3種類ある。

座右の書Notes/Domino APIプログラミング―C++とSTLによる実践的プログラミングでは、紙面の都合で代表的なCDデータのクラス実装例を紹介するにとどめていますが、一方でどんなCDデータでも取り込んだり書き込んだりできる「CdOtherクラス」というのを紹介していました。ポイントは、CDデータのポインタが渡されると、ヘッダー情報を正しく分析して、BSIG、WSIG、LSIGのいずれかであることを判別して、コンテンツ情報を切り分けるようになっていることです。ヘッダー情報とコンテンツ情報を正しく分離できれば、あとの作業は共通化することができます。

本プロジェクトのNSFinderでは、いったんはCDTEXTのみを判別してテキスト化し、その他のCDデータはいったん無視する方針で実装しましたが、やはりきちんと実装していきたいので、まずはCdOtherのような汎用CDデータクラスで処理できるようにします。

// ntlx/cd.h

#ifndef NTLX_CD_H
#define NTLX_CD_H

#include <ntlx_global.h>
#include <ntlx/status.h>

namespace ntlx
{

class NTLXSHARED_EXPORT Cd
{
public:
  virtual WORD getSignature() const = 0;
  virtual WORD odsLength() const = 0;
  virtual QString toString() const = 0;
};

} // namespace ntlx

#endif // NTLX_CD_H

まず、元となるクラス「ntlx::Cd」です。シグネチャの取得、データ長の取得、そして文字列に変換するメソッドをすべて純粋仮想関数で定義しているインターフェースクラスです。

これを継承した汎用クラスが「ntlx::cd::Other」です。

// ntlx/cd/other.h

#ifndef NTLX_CD_OTHER_H
#define NTLX_CD_OTHER_H

#include <ntlx/cd.h>

namespace ntlx
{
namespace cd
{

class NTLXSHARED_EXPORT Other
    : public Cd
{
public:
  Other(char** ppRecord);
  Other(const Other& other);
  Other& operator=(const Other& other);

  virtual WORD getSignature() const;
  virtual WORD odsLength() const;
  virtual QString toString() const;

protected:
  QByteArray record_;
};

} // namespace cd

} // namespace ntlx

#endif // NTLX_CD_OTHER_H

本家Notes/Domino APIプログラミング―C++とSTLによる実践的プログラミングでは、CdOtherとして紹介されていましたが、ここでは2層の名前空間の中で定義しています。また、データ本体の扱い方を、char文字配列からQByteArrayに変更しました。QByteArrayの方が扱いがはるかに楽であるためです。

// cd/other.cpp

#include "ntlx/cd/other.h"
#include <QStringList>

#if defined(NT)
#pragma pack(push, 1)
#endif

#include <ods.h>

#if defined(NT)
#pragma pack(pop)
#endif

namespace ntlx
{
namespace cd
{

Other::Other(char** ppRecord)
  : Cd()
  , record_()
{
  int len = 0;
  switch (((uchar*)*ppRecord)[1])
  {
  case 0x00: // Long Signature
    len = ((LSIG*)*ppRecord)->Length;
    break;

  case 0xff: // Word Signature
    len = ((WSIG*)*ppRecord)->Length;
    break;

  default: // Byte Signature
    len = ((BSIG*)*ppRecord)->Length;
    break;
  }

  record_ = QByteArray(*ppRecord, len);
}

Other::Other(const Other& other)
  : Cd()
  , record_(other.record_)
{
}

Other& Other::operator=(const Other& other)
{
  if (this != &other)
    record_ = other.record_;
  return *this;
}

WORD Other::getSignature() const
{
  switch (((uchar*)record_.constData())[1])
  {
  case 0x00: return ((LSIG*)record_.constData())->Signature;
  case 0xff: return ((WSIG*)record_.constData())->Signature;
  }
  return ((BSIG*)record_.constData())->Signature;
}

WORD Other::odsLength() const
{
  return (WORD)(record_.length() + record_.length() % 2);
}

QString Other::toString() const
{
  QStringList list;
  for (auto it = record_.constBegin(); it != record_.constEnd(); ++it)
  {
    uchar b = (uchar)(*it);
    list.append(QString("%1").arg((uint)b, 4, 16, QChar('0')).right(2));
  }
  return list.join(" ");
}

} // namespace cd

} // namespace ntlx

ほとんど本家Notes/Domino APIプログラミング―C++とSTLによる実践的プログラミングを参考にしています。

odsLengthメソッドは実データサイズではなくODSとしてメモリ上にどれくらいの長さを持っているかということで、これが必ず偶数値を取ることから、「~% 2」の値が加算されています。

toStringメソッドはQtのライブラリを使って、バイトデータを16進数文字列に変換しています。

このOtherクラスを用いてNSFinderを実装した例(Windows)をご紹介しておきます。

f:id:takahide-kondoh:20170813221500p:plain