unit mPegFilter;

interface

uses
  Classes, SysUtils, PerlRegEx;

type
  // analyse Peg Num
  // YK1+000 --> K1+000
  TPegFilter = class
  private
    FBeginPeg: string;
    FBeginPegNum: Double;
    FEndPeg: string;
    FEndPegNum: Double;

    function PegFormat(const AStr: string): string;
    function FilterPeg(const AStr: string; var APeg: string; var ANum: Double; var ABegin, AEnd: Integer): Boolean;
  public
    function Filter(const AStr: string): Boolean;

    property BeginPeg: string read FBeginPeg;
    property BeginPegNum: Double read FBeginPegNum;
    property EndPeg: string read FEndPeg;
    property EndPegNum: Double read FEndPegNum;
  end;

  // filter begin/end Peg, keep orginal Peg
  // GLK1+000 --> GLK1+000
  TPegInfo = class
    FPeg: string;
    FOffset: Integer;
    FLength: Integer;
  end;

  TPegStrFilter = class
  private
    FToRegStr: string;
    FPegRegStr: string;
    FPegStr: string;

    FPegList: TList;
    FBeginPegList: TList;
    FEndPegList: TList;

    procedure ClearHistory;
    procedure FilterPeg;
    procedure FilterBeginAndEnd;
    procedure AnalysePeg;

    function MergePeg(Pegs: TList): string;

    procedure SetPegStr(const Value: string);
    function GetBeginPeg: string;
    function GetEndPeg: string;
    function GetSubPegCount: Integer;
    function GetSubPeg(AIndex: Integer): string;
  public
    constructor Create;
    destructor Destory;

    property PegRegStr: string read FPegRegStr;
    property ToRegStr: string read FToRegStr;

    property PegStr: string read FPegStr write SetPegStr;
    property BeginPeg: string read GetBeginPeg;
    property EndPeg: string read GetEndPeg;
    property SubPegCount: Integer read GetSubPegCount;
    property SubPeg[AIndex: Integer]: string read GetSubPeg;
  end;

function PegFilter: TPegFilter;

implementation

uses Math, ZhAPI;

var
  FPegFilter: TPegFilter;

function PegFilter: TPegFilter;
begin
  if FPegFilter = nil then
    FPegFilter := TPegFilter.Create;
  Result := FPegFilter;
end;

{ TPegFilter }

function TPegFilter.Filter(const AStr: string): Boolean;
var
  sPeg: string;
  iPos, iBegin, iEnd: Integer;
begin
  sPeg := PegFormat(AStr);
  Result := FilterPeg(sPeg, FBeginPeg, FBeginPegNum, iBegin, iEnd);
  if Result and (sPeg[iEnd+1] = '~') then
  begin
    sPeg := StringReplace(sPeg, FBeginPeg, '', []);
    FilterPeg(sPeg, FEndPeg, FEndPegNum, iBegin, iEnd);
    if FEndPegNum = 0 then
    begin
      FEndPeg := FBeginPeg;
      FEndPegNum := FBeginPegNum;
    end
    else
      Result := FEndPegNum >= FBeginPegNum;
  end
  else
  begin
    FEndPeg := '';
    FEndPegNum := 0;
  end;
end;

function TPegFilter.FilterPeg(const AStr: string; var APeg: string;
  var ANum: Double; var ABegin, AEnd: Integer): Boolean;

  function FilterInt(ABegin: Integer; var AEnd: Integer; var AIntStr: string; var AValue: Integer): Boolean;
  var
    iPos, iLength: Integer;
    sInt: string;
  begin
    AIntStr := '';
    AEnd := ABegin;

    iPos := ABegin;
    iLength := Length(AStr);
    while (iPos <= iLength) do
    begin
      if AStr[iPos] in ['0'..'9'] then
      begin
        AIntStr := AIntStr + AStr[iPos];
        AEnd := iPos;
      end
      else Break;
      Inc(iPos);
    end;

    Result := AIntStr <> '';
    AValue := StrToIntDef(AIntStr, 0);
  end;

  // 过滤一个小于1000的Float
  function FilterFloat(ABegin: Integer; var AEnd: Integer; var AFloatStr: string; var AValue: Double): Boolean;
  var
    iPos, iLength, iIntPartLength: Integer;
    bHasPoint, bAllZero: Boolean;
  begin
    AFloatStr := '';
    AEnd := ABegin;

    iPos := ABegin;
    iLength := Length(AStr);
    bHasPoint := False;
    bAllZero := True;
    iIntPartLength := 0;
    while (iPos <= iLength) do
    begin
      if AStr[iPos] in ['0'..'9'] then
      begin
        AFloatStr := AFloatStr + AStr[iPos];
        AEnd := iPos;
        if not bHasPoint then
          Inc(iIntPartLength);

        bAllZero := bAllZero and (AStr[iPos] = '0');
      end
      else if (AStr[iPos] = '.') and (not bHasPoint) then
      begin
        AFloatStr := AFloatStr + AStr[iPos];
        AEnd := iPos;
        bHasPoint := True;
      end
      else Break;
      Inc(iPos);
    end;

    if iIntPartLength > 3 then
    begin
      AEnd := ABegin + 3 - 1;
      AFloatStr := Copy(AFloatStr, 1, 3);
    end;

    AValue := StrToFloatDef(AFloatStr, 0);
    Result := (AValue <> 0) or (bAllZero and (AEnd > ABegin));
  end;

var
  iPos, iLength: Integer;
  iBeginNum, iEndNum, iPosPuls, iBeginNum2, iEndNum2: Integer;
  sNum, sNum2: string;
  iNum: Integer;
  fNum: Double;
begin
  Result := False;
  APeg := '';
  ANum := 0;
  ABegin := -1;
  AEnd := -1;

  iPos := 1;
  iLength := Length(AStr);
  while (iPos <= iLength) and (not Result) do
  begin
    if AStr[iPos] = 'k' then
    begin
      iBeginNum := iPos + 1;
      if FilterInt(iBeginNum, iEndNum, sNum, iNum) then
      begin
        iPosPuls := iEndNum + 1;
        if AStr[iPosPuls] = '+' then
        begin
          iBeginNum2 := iPosPuls + 1;
          if FilterFloat(iBeginNum2, iEndNum2, sNum2, fNum) then
          begin
            Result := True;
            APeg := Copy(AStr, iPos, iEndNum2-iPos+1);
            ANum := iNum * 1000 + fNum;
            ABegin := iPos;
            AEnd := iEndNum2;
          end;
        end;
      end
    end;
    inc(iPos);
  end;
end;

function TPegFilter.PegFormat(const AStr: string): string;
begin
  Result := AStr;
  Result := StringReplace(Result, 'K', 'k', [rfReplaceAll]);
  Result := StringReplace(Result, '＋', '+', [rfReplaceAll]);
  Result := StringReplace(Result, '～', '~', [rfReplaceAll]);
  Result := StringReplace(Result, ' ', '', [rfReplaceAll]);
end;

{ TPegStrFilter }

procedure TPegStrFilter.AnalysePeg;
begin
  ClearHistory;
  FilterPeg;
  FilterBeginAndEnd;
end;

procedure TPegStrFilter.ClearHistory;
begin
  ClearObjects(FPegList);
  FPegList.Clear;
  FBeginPegList.Clear;
  FEndPegList.Clear;
end;

constructor TPegStrFilter.Create;
begin
  FPegRegStr := '[a-zA-z]{0,2}[kK]\d+[+＋][0-9.]+';
  FToRegStr := '[-－~～]';

  FPegList := TList.Create;
  FBeginPegList := TList.Create;
  FEndPegList := TList.Create;
end;

destructor TPegStrFilter.Destory;
begin
  FEndPegList.Free;
  FBeginPegList.Free;
  ClearObjects(FPegList);
  FPegList.Free;
end;

procedure TPegStrFilter.FilterBeginAndEnd;
var
  vToReg: TPerlRegEx;
  vPeg1, vPeg2: TPegInfo;
  i, iPos: Integer;
begin
  vToReg := TPerlRegEx.Create;
  vToReg.RegEx := FToRegStr;
  if FPegList.Count > 0 then
  begin
    for i := 1 to FPegList.Count - 1 do
    begin
      iPos := i;
      vPeg1 := TPegInfo(FPegList.Items[i - 1]);
      vPeg2 := TPegInfo(FPegList.Items[i]);
      vToReg.Subject := Copy(FPegStr, vPeg1.FOffSet + vPeg1.FLength, vPeg2.FOffSet - (vPeg1.FOffSet + vPeg1.FLength));
      if vToReg.Match then
        Break;
    end;

    for i := 0 to FPegList.Count - 1 do
    begin
      if i < iPos then
        FBeginPegList.Add(FPegList.Items[i])
      else
        FEndPegList.Add(FPegList.Items[i]);
    end;
  end;
  vToReg.Free;
end;

procedure TPegStrFilter.FilterPeg;
var
  vPegReg: TPerlRegEx;
  vPegInfo: TPegInfo;
begin
  vPegReg := TPerlRegEx.Create;
  vPegReg.Subject := PegStr;
  vPegReg.RegEx := PegRegStr;
  while vPegReg.MatchAgain do
  begin
    vPegInfo := TPegInfo.Create;
    vPegInfo.FPeg := vPegReg.MatchedText;
    vPegInfo.FOffset := vPegReg.MatchedOffset;
    vPegInfo.FLength := vPegReg.MatchedLength;
    FPegList.Add(vPegInfo);
  end;
  vPegReg.Free;
end;

function TPegStrFilter.GetBeginPeg: string;
begin
  Result := MergePeg(FBeginPegList);
end;

function TPegStrFilter.GetEndPeg: string;
begin
  if FEndPegList.Count > 0 then
    Result := MergePeg(FEndPegList)
  else
    Result := MergePeg(FBeginPegList);
end;

function TPegStrFilter.GetSubPeg(AIndex: Integer): string;
begin
  if (AIndex < 0) or (AIndex >= SubPegCount) then
    Result := ''
  else
    Result := TPegInfo(FPegList.Items[AIndex]).FPeg;
end;

function TPegStrFilter.GetSubPegCount: Integer;
begin
  Result := FPegList.Count;
end;

function TPegStrFilter.MergePeg(Pegs: TList): string;
var
  i: Integer;
begin
  Result := '';
  for i := 0 to Pegs.Count - 1 do
  begin
    if i = 0 then
      Result := TPegInfo(Pegs.Items[i]).FPeg
    else
      Result := Result + '/' + TPegInfo(Pegs.Items[i]).FPeg;
  end;
end;

procedure TPegStrFilter.SetPegStr(const Value: string);
begin
  FPegStr := Value;
  AnalysePeg;
end;

initialization
  FPegFilter := nil;
finalization
  FPegFilter.Free;
end.
