unit IUtils;

interface

uses
	Classes, Parsers, XLists;

type
	TStringFile = class
	private
		FFile: TFileStream;
    FParser: TTextReader;
    FLineNo: Integer;

    function GetConvertOemToChar: Boolean;
    procedure SetConvertOemToChar(const Value: Boolean);
	public
  	constructor Create(const aFileName: String; aBuffSize: Integer = 10*1024);
		destructor Destroy; override;

		function  ReadString: String;
		procedure SkipString;
		function  Eof: Boolean;

    property ConvertOemToChar: Boolean read GetConvertOemToChar write SetConvertOemToChar;
    property LineNo: Integer read FLineNo;
	end;

function GetStringTail(const aString: String; aPos: Integer): String;

// Splits string to TStrings
// Notes: can produce empty strings;
procedure SplitStringToStrings(const aSrcString, aDelimiter: String; aStrings: TStrings);
procedure SplitStringTo2Strings(const aSrcString, aDelimiter: String;
	out aString1, aString2: String);
procedure SplitStringTo2StringsFirstWithQuotes(const aSrcString,
	aDelimiter: String; out aString1, aString2: String);
function SplitStringWithKeyword(const aSrcString, aKeyWord: String; out aTail: String): Boolean;
// trims and reduces string bounds 1 char example '  (test)' -> 'test'
function DeflateString(const s: String): String;
function DeflateStr(const s: String; c1, c2: char): String;

implementation

uses
	Windows, SysUtils, AClasses;

function GetStringTail(const aString: String; aPos: Integer): String;
var
	aLength: Integer;
begin
	aLength := Length(aString);
	if aPos > aLength then
		Result := ''
	else
		Result := Copy(aString, aPos, aLength - aPos + 1);
end;

// Splits string to TStrings
// Notes: can produce empty strings;
procedure SplitStringToStrings(const aSrcString, aDelimiter: String; aStrings: TStrings);
var
	aTail: String;
	aPos, aDelimiterLength: Integer;
begin
	aStrings.Clear;
	aDelimiterLength := Length(aDelimiter);
	aTail := aSrcString;
	while True do begin
		aPos := Pos(aDelimiter, aTail);
		if aPos = 0 then begin
			aStrings.Add(Trim(aTail));
			Exit;
		end;
		aStrings.Add(Trim(Copy(aTail, 1, aPos - 1)));
		aTail := GetStringTail(aTail, aPos + aDelimiterLength);
	end;
end;

procedure SplitStringTo2Strings(const aSrcString, aDelimiter: String;
	out aString1, aString2: String);
var
	aPos, aDelimiterLength: Integer;
begin
	aDelimiterLength := Length(aDelimiter);
	aPos := Pos(aDelimiter, aSrcString);
	if aPos = 0 then begin
		aString1 := Trim(aSrcString);
		aString2 := '';
		Exit;
	end;
	aString1 := Trim(Copy(aSrcString, 1, aPos - 1));
	aString2 := Trim(GetStringTail(aSrcString, aPos + aDelimiterLength));
end;

procedure SplitStringTo2StringsFirstWithQuotes(const aSrcString,
	aDelimiter: String; out aString1, aString2: String);
var
	aTail: String;
	aPos: Integer;
begin
	aTail := Trim(aSrcString);
	if (aTail = '') or (aTail[1] <> '"') then
		raise Exception.Create('Неправильный формат строки. Не найдены открывающие кавычки.');
	aString1 := '';
	while True do begin
		Delete(aTail, 1, 1);
		aPos := Pos('"', aTail);
		if aPos = 0 then
			raise Exception.Create('Неправильный формат строки. Не найдены закрывающие кавычки.');
		aString1 := aString1 + Copy(aTail, 1, aPos - 1);
		Delete(aTail, 1, aPos);
		if (aTail = '') or (aTail[1] <> '"') then
			break;
		aString1 := aString1 + '"';
	end;
	aString1 := '"' + aString1 + '"';
	aTail := Trim(aTail);
	if aTail = '' then
		aString2 := ''
	else begin
		if Copy(aTail, 1, Length(aDelimiter)) <> aDelimiter then
			raise Exception.Create('Не найден разделитель значений.');
		aString2 := Trim(GetStringTail(aTail, Length(aDelimiter) + 1));
	end;
end;

function SplitStringWithKeyword(const aSrcString, aKeyWord: String; out aTail: String): Boolean;
var
	sSource, sKey: String;
begin
	sSource := Trim(aSrcString);
	sKey := Trim(aKeyWord);
	Result := Pos(AnsiUpperCase(sKey), AnsiUpperCase(sSource)) = 1;
	if Result then
		aTail := Trim(GetStringTail(sSource, 1 + Length(sKey)));
end;

function DeflateString(const s: String): String;
var
	len: Integer;
begin
	Result := Trim(s);
	len := Length(Result);
	if len < 2 then
		raise Exception.Create('Internal error in IUtils.DeflateString');
	Result := Copy(Result, 2, len - 2);
end;

function DeflateStr(const s: String; c1, c2: char): String;
var
	len: Integer;
begin
	Result := Trim(s);
	len := Length(Result);
	if len < 2 then
		raise Exception.Create('Строка не заключена в кавычки');
	if (Result[1] <> c1) or (Result[len] <> c2) then
		raise Exception.Create('Строка не имеет соответсвующей кавычки');
	Result := Copy(Result, 2, len - 2);
end;

{ TStringFile }

constructor TStringFile.Create(const aFileName: String; aBuffSize: Integer);
begin
	inherited Create;
  FFile := TFileStream.Create(aFileName, fmOpenRead or fmShareDenyWrite);
  FParser := TTextReader.Create(FFile, aBuffSize);
  FLineNo := 1;
end;

destructor TStringFile.Destroy;
begin
  FParser.Free;
  FFile.Free;
	inherited Destroy;
end;

function TStringFile.Eof: Boolean;
begin
  Result := FParser.Eof;
end;

function TStringFile.GetConvertOemToChar: Boolean;
begin
  Result := FParser.ConvertOemToChar;
end;

procedure TStringFile.SetConvertOemToChar(const Value: Boolean);
begin
  FParser.ConvertOemToChar := Value;
end;

function TStringFile.ReadString: String;
begin
	Result := FParser.ReadLine;
	Inc(FLineNo);
end;

procedure TStringFile.SkipString;
begin
	ReadString;
end;

end.
