文字列内の部分文字列が単語かどうかをチェックする

 文字列内から単語を検索する際に、検索した部分文字列が単語かどうかを全角文字を考慮して判断するユニットです。文字列が半角英数字のみであれば、検索した部分文字列の前後がホワイトスペース,もしくは記号かどうかをチェックするだけで済みますが、「今日はパソコンを使わなかった」等の全角文字が含まれる文字列から「パソコン」を単語として認識させるためには、前後文字の種類も判別しないといけません。
 以下のユニットでは検索した部分文字列が全角文字の場合には、その種類(ひらがな,カタカナ,漢字,英数字)もチェックして、前後の文字種が同じかどうかを調べることで、ある程度の全角単語を認識させることが出来ます(ある程度と言うのは、検索語がひらがなのみ,カタカナのみ等の場合にのみ単語として認識することが可能という意味です)。

 以下のソースをファイル名「IsWholeWd.pas」として保存して下さい。使用する場合にはuses節にIsWholeWdを追加するだけです。

(*
  IsWholeWdユニット
  文字列中の指定した語句が単語かどうかをチェックする

  使用例)
  FoundAt := AnsiPos(FindStr, Str);
  if (FoundAt > 0) and IsWholeWord(Str, FoundAt, Length(FindStr) then
    見つけた処理...

  前後の区切り子を確認することで検索した語句が単語かどうかを判断する
  全角文字の場合には、前後の文字が漢字,ひらがな,カタカナ,英字
  の違いもチェックすることで、"ある程度"の単語判断が可能

  Copyright(c) 2001 M&I
  mailto:masahiro.inoue@nifty.ne.jp
  http://member.nifty.ne.jp/m-and-i/
*)

unit IsWholeWd;

interface

function IsWholeWord(const S: string; const StartPos, FindLen: integer): Boolean;

implementation

uses SysUtils;

const
  // 半角区切り子
  ANKSEP = #9#10#13' !"#$%&''()=~^\|{}[]`@;+:*,<.>/?';
  // 全角区切り子
  ZENSEP = ' 、。,.・:;?!゛゜´`¨^ ̄_〇ー―‐/\〜‖|…‥'+
           '‘’“”()〔〕[]{}〈〉《》「」『』【】+−±×÷=≠'+
           '<>≦≧∞∴♂♀°′″℃¥$¢£%#&*@§☆★○●◎◇◆'+
           '□■△▲▽';

//  S:        文字列全体
//  StartPos: 検索された位置
//  FindLen:  検索された長さ
//  Result:   単語ならTrue
function IsWholeWord(const S: string; const StartPos, FindLen: integer): Boolean;
var
  sr, er,
  knj, knjh, knjk,
  knja, knjz,
  ank, kana: Boolean;
  cc, ks, ke,
  EndPos: integer;
  // 速度を考えるとCのようにマクロで展開したいところ...
  function IsKanji(const ch: Char): Boolean;
  begin
    Result := Ord(Ch) > $7F;
  end;
  function IsKanji2(const ch: Char): Boolean;
  var
    c: Byte;
  begin
    c := Ord(ch);
    Result := (c in [$40..$7E]) or (c in [$80..$FC]);
  end;
  function IsKana(const ch: Char): Boolean;
  begin
    Result := Ord(ch) in [$A1..$DF];
  end;
  function isZHira(Ch: integer): Boolean;
  begin
    Result := (Ch >= $829F) and (Ch <= $82F1);
  end;
  function isZKana(Ch: integer): Boolean;
  begin
    Result := (Ch >= $8340) and (Ch <= $8396);
  end;
  function isZAnk(Ch: integer): Boolean;
  begin
    Result := (Ch >= $824F) and (Ch <= $829A);
  end;
  function isZKnj(Ch: integer): Boolean;
  begin
    Result := Ch >= $889F;
  end;
begin
  if S = '' then
  begin
    Result := False;
    Exit;
  end;
  EndPos := StartPos + FindLen - 1;
  sr := StartPos = 1;
  er := EndPos = Length(S);
  // 行にこの単語しかない
  if sr and er then
  begin
    Result := True;
    Exit;
  end;
  knj  := False;
  knjk := False;
  knjh := False;
  knja := False;
  knjz := False;
  kana := False;
  ank := False;
  cc := EndPos - StartPos;
  // 検索語が1文字以上の場合文字の種類をチェックする
  if cc > 0 then
  begin
    // 最初と最後が漢字
    if IsKanji(S[StartPos]) and IsKanji2(S[StartPos+1]) then
    begin
      if IsKanji(S[EndPos-1]) and IsKanji2(S[EndPos]) then
        knj := True;
    end else if IsKana(S[StartPos]) and Iskana(S[EndPos]) then
      kana := True
    else if (Ord(S[StartPos]) < $80) and (Ord(S[EndPos]) < $80) then
      ank := True;
  end else begin
    if IsKana(S[StartPos]) then
      kana := True
    else if Ord(S[StartPos]) < $80 then
      ank := True;
  end;
  //全角の種類を確認する
  if knj then
  begin
    ks := Ord(S[StartPos]) shl 8 + Ord(S[StartPos+1]);
    ke := Ord(S[EndPos-1]) shl 8 + Ord(S[EndPos]);
    //全角カナ
    if IsZKana(ks) and IsZKana(ke) then
      knjk := True
    //全角ひらがな
    else if IsZHira(ks) and IsZHira(ke) then
      knjh := True
    //全角英数字
    else if IsZAnk(ks) and IsZAnk(ke) then
      knja := True
    //漢字
    else if IsZKnj(ks) and IsZKnj(ke) then
      knjz := True;
  end;
  // 検索語が半角英数字
  // 前後が半角英数字以外もしくはセパレータ文字かどうかを確認
  if ank then
  begin
    if not sr then
    begin
      // 前が半角カタカナ
      if Iskana(S[StartPos-1]) then
      begin
        sr := True;
      // 前が全角
      end else if StartPos > 2 then
      begin
        if IsKanji(S[StartPos-2]) and IsKanji2(S[StartPos-1]) then
          sr := True
        else
          sr := AnsiPos(S[StartPos-1], ANKSEP) > 0;
      end else begin
        sr := AnsiPos(S[StartPos-1], ANKSEP) > 0;
      end;
    end;
    if not er then
    begin
      // 後が半角カタカナ
      if Iskana(S[StartPos+1]) then
      begin
        er := True;
      // 後が全角
      end else if EndPos < (Length(S) - 1) then
      begin
        if IsKanji(S[EndPos+1]) and IsKanji2(S[EndPos+2]) then
          er := True
        else
          er := AnsiPos(S[EndPos+1], ANKSEP) > 0;
      end else begin
        er := AnsiPos(S[EndPos+1], ANKSEP) > 0;
      end;
    end;
  //検索語が半角カタカナ
  //前後が半角英数字,全角,セパレータかどうかを確認
  end else if kana then
  begin
    if not sr then
    begin
      // 前が半角
      if Ord(S[StartPos-1]) < $80 then
      begin
        sr := True;
      // 前が全角
      end else if StartPos > 2 then
      begin
        if IsKanji(S[StartPos-2]) and IsKanji2(S[StartPos-1]) then
          sr := True;
      end else begin
        sr := AnsiPos(S[StartPos-1], ANKSEP) > 0;
      end;
    end;
    if not er then
    begin
      // 後が半角
      if Ord(S[StartPos+1]) < $80 then
      begin
        er := True;
      // 後が全角
      end else if EndPos < (Length(S) - 1) then
      begin
        if IsKanji(S[EndPos+1]) and IsKanji2(S[EndPos+2]) then
          er := True;
      end else begin
        er := AnsiPos(S[EndPos+1], ANKSEP) > 0;
      end;
    end;
  //検索語が全角文字
  end else if knj then
  begin
    // 前をチェック
    if StartPos > 2 then
    begin
      //前も漢字か
      if IsKanji(S[StartPos-2]) then
      begin
        //ひらがな,カナ,英数字のチェック
        //漢字
        if knjz then
        begin
          if not IsZKnj(Ord(S[StartPos -2]) shl 8 + Ord(S[StartPos-1])) then
            sr := True;
        //全角カタカナ
        end else if knjk then
        begin
          if not IsZKana(Ord(S[StartPos -2]) shl 8 + Ord(S[StartPos-1])) then
            sr := True;
        //全角ひらがな
        end else if knjh then
        begin
          if not IsZHira(Ord(S[StartPos -2]) shl 8 + Ord(S[StartPos-1])) then
            sr := True;
        //全角英数字
        end else if knja then
        begin
          if not IsZAnk(Ord(S[StartPos -2]) shl 8 + Ord(S[StartPos-1])) then
            sr := True;
        //全角カタカナ
        end else begin
          if AnsiPos(S[StartPos - 2] + S[StartPos - 1], ZENSEP) > 0 then
            sr := True
          else
            sr := False;
        end;
      end else
        sr := True;
    // 前に1文字しかないので全角ではない
    end else
      sr := True;
    // 後をチェック
    if EndPos < (Length(S) - 1) then
    begin
      //後も漢字か
      if IsKanji(S[EndPos+1]) then
      begin
        //ひらがな,カナ,英数字のチェック
        if knjz then
        begin
          if not IsZKnj(Ord(S[EndPos+1]) shl 8 + Ord(S[EndPos+2])) then
            er := True;
        end else if knjk then
        begin
          if not IsZKana(Ord(S[EndPos+1]) shl 8 + Ord(S[EndPos+2])) then
            er := True;
        end else if knjh then
        begin
          if not IsZHira(Ord(S[EndPos+1]) shl 8 + Ord(S[EndPos+2])) then
            er := True;
        end else if knja then
        begin
          if not IsZAnk(Ord(S[EndPos+1]) shl 8 + Ord(S[EndPos+2])) then
            er := True;
        end else begin
          if AnsiPos(S[EndPos + 1] + S[EndPos + 2], ZENSEP) > 0 then
            er := True
          else
            er := False;
        end;
      end else
          er := True;
    end else
      er := True;
  //その他(最初と最後の文字種が違う場合)
  //前後が半角と全角のセパレータかどうかを確認
  end else begin
    if not sr then
    begin
      if AnsiPos(S[StartPos-1], ANKSEP) > 0 then
      begin
        sr := True;
      end else if StartPos > 2 then
      begin
        if AnsiPos(S[StartPos - 2] + S[StartPos - 1], ZENSEP) > 0 then
          sr := True;
      end;
    end;
    if not er then
    begin
      if AnsiPos(S[EndPos + 1], ANKSEP) > 0 then
      begin
        er := True;
      end else if Length(S) - 1 > EndPos then
      begin
        if AnsiPos(S[EndPos + 1] + S[EndPos + 2], ZENSEP) > 0 then
          er := True;
      end;
    end;
  end;
  //前と後ろ両方がセパレータならワードと見なす
  Result := sr and er;
end;

end.