////////////////////////////////////////////////////////////
//HTMLのテキストファイル編集ユニット
//00/05/17 リンクの種類によってMakeLinkListの返す値を変更しました
//00/07/31 MakeImgLinkのコードを追加しました。
//00/08/03
// MakeImgListをBackgroundで指定された画像を列挙するように改造しました。
// AddIndexhtml関数を追加しました。
// StringAddReplaceIgnoreAllの説明をわかりやすくしました。
//00/08/04
// PutSlashMark/RemoveSlashMarkを変更
//00/08/08
// MakeImgListの実装を完全に変更
// 拡張子を含むテキスト検索で取得する方法に変えました
// それに伴いAnsiPosRange関数なども含めました。
////////////////////////////////////////////////////////////
unit HtmlTxtCtrl;
interface
uses
SysUtils, Classes, Dialogs, Math,
DelimiterCut; //このユニットが必要です
type
TTagRemainFlag = (trPerfect, trRemainCRLF, trTagCRLF);
{TagRemainで使う
trPerfect 完全にタグだけを残す(タグ外はCRLFもなくなる)
trRemainCRLF タグ外のCRLFは削除しない
trTagCRLF 終タグの後に常にCRLFを代入}
TLinkList = (llFull, llExternal, llInternal, llMail);
{MakeLinkListで使う
llFull すべてのリンクを列挙する
llExternal 外部リンクを列挙する
llInternal 内部リンクを列挙する
llMail メイルリンクを列挙する}
TImgList = (ilFull, ilExternal, ilInternal);
{MakeImgListで使う
ilFuil すべてのリンクを列挙する
ilExternal 外部リンクを列挙する
ilInternal 内部リンクを列挙する}
procedure MakeLinkList(const HtmlText: String; LinkList: TStrings;
LinkListType: TLinkList);
//・HTMLファイルのリンクリストを作ります。
// A HREFで指定されたURLのリストをTStringsに掘り込みます
procedure MakeImgList(const HtmlText: String; LinkList: TStrings;
ImgListType: TImgList);
//・HTMLファイルのイメージリンクリストを作ります
function HTMLTagDelete(const HtmlText: String): String;
//・HTMLテキストからタグ<>で囲まれた部分を削除します
function TagDelete(const S, StartTag, EndTag: String): String;
//・テキストから任意のタグで囲まれた部分を削除
function HTMLTagRemain(const HtmlText: String;
TagRemainFlag: TTagRemainFlag): String;
//・HTMLテキストからタグ<>で囲まれた以外の部分を削除します
function TagRemain(const S, StartTag, EndTag: String;
TagRemainFlag: TTagRemainFlag): String;
//・テキストから任意のタグで囲まれた以外の部分を削除
function ExtractAbsoluteURL(const BaseURL, RelativeURL: string): string;
//URLの相対アドレスから絶対アドレスを求める
function AddIndexhtml(URL: String): String;
//フォルダ名URLなどにindex.htmlをつける
implementation
////////////////////////////////////////////////////////////
//ライブラリ内部共通関数
////////////////////////////////////////////////////////////
//Sの中にSubStrがいくつあるかカウントする
//大文字小文字は区別する
function StrCount(const Substr, S: string): Cardinal;
var
Str: String;
begin
Result := 0;
if (Substr = '') or (S = '') then
begin
raise Exception.Create('カウント文字列不正エラー');
end;
Str := S;
while AnsiPos( Substr, Str) <> 0 do
begin
Inc(Result);
delete(Str, AnsiPos(Substr, Str), Length(Substr));
end;
end;
//文字列の最後が'/'or'\'ならそれを取り除く
function RemoveSlashMark(const S: TFileName): TFileName;
begin
Result := S;
if Length(Result) = LastDelimiter('\/', Result) then
SetLength(Result, Length(Result)-1);
end;
//文字列の最後が'\'なら'/'に置き換え
//それでも文字列の最後が'/'じゃないなら追加
function PutSlashMark(const S: TFileName): TFileName;
begin
Result := RemoveSlashMark(S);
Result := Result + '/';
end;
//AnsiPosの大小文字区別無し版
function AnsiIgnoreCasePos(const Substr, S: string): Integer;
begin
Result := AnsiPos( AnsiUpperCase(Substr), AnsiUpperCase(S));
end;
//CRLF以外のものをDeleteする
//Deleteの範囲にCRLFが含まれている場合CRLFを後から追加
//CRのみLFのみには対応しない
procedure DeleteExceptCRLF(var s : AnsiString; index, count : Integer);
var
i, CRLFCount: Integer;
begin
CRLFCount := StrCount(#13#10, copy(s, index, count));
Delete( S, index, count);
for i := 0 to CRLFCount - 1 do
begin
Insert( #13#10, s, index);
end;
end;
//-------------------------------
//特殊な置き換え関数
{
文字列を追加して置き換える
AAAという文字を検索してAAAxxxという値を代入します
検索に大文字小文字は区別しません
StringReplaceと何が違うかというと
Result := StringReplace( Result, EndTag, EndTag+#13#10, [rfReplaceAll, rfIgnoreCase]);
Result := StringAddReplaceIgnoreAll( Result, EndTag, #13#10);
この↑のやり方ではEndTagの内容が
大小文字が合わないのでStringAddReplaceEgnoreAllでは独自コーディングしています。
}
//
function StringAddReplaceIgnoreAll(const S, SearchPattern, AddPattern: String):
String;
var
i, LoopCount, FindIndex: Integer;
Str: String;
begin
Str := S;
Result := '';
LoopCount := StrCount( AnsiUpperCase(SearchPattern), AnsiUpperCase(S));
for i := 0 to LoopCount - 1 do
begin
FindIndex := AnsiIgnoreCasePos( SearchPattern, Str);
Insert(AddPattern, Str, FindIndex + Length(SearchPattern));
Result := Result +
copy(Str, 1, FindIndex + Length(SearchPattern)-1 + Length(AddPattern)-1);
Delete(Str, 1, FindIndex + Length(SearchPattern)-1 + Length(AddPattern)-1);
end;
Result := Result + Str
end;
type TPosFunction = function(const Substr, S: string): Integer;
{typeはimplementation内で突然宣言してもOKだよ}
//-------------------------------
//指定範囲検索基本関数...これは直接使わない
function AnsiPosRangeBase(SearchFunc: TPosFunction;
const SubStr, S: String; StartIndex, EndIndex: Integer): Integer;
var
Str: String;
FindIndex: Integer;
begin
Result := 0;
if (Substr = '') or (S = '') then exit;
if not (StartIndex <= EndIndex) then exit;
{↑↓見事なエラー処理だ}
if (StartIndex < 1) then exit;
if (Length(S) < EndIndex) then EndIndex := Length(S);
{EndIndexだけはエラー処理を甘くしてみた。厳しくするなら↓のようにどうぞ}
// if (StartIndex < 1) or (Length(S) < EndIndex) then exit;
Str := copy(S, StartIndex, EndIndex - StartIndex + 1);
FindIndex := SearchFunc( SubStr, Str);
if FindIndex = 0 then
Result := 0
else
Result := StartIndex + FindIndex - 1;
end;
//-------------------------------
//指定範囲を検索するAnsiPos
function AnsiPosRange(const SubStr, S: String;
StartIndex, EndIndex: Integer): Integer;
begin
Result := AnsiPosRangeBase(AnsiPos, SubStr, S, StartIndex, EndIndex);
end;
//-------------------------------
//指定範囲を検索するBackAnsiPos
function BackAnsiPosRange(const SubStr, S: String;
StartIndex, EndIndex: Integer): Integer;
begin
Result := AnsiPosRangeBase(BackAnsiPos, SubStr, S, StartIndex, EndIndex);
end;
////////////////////////////////////////////////////////////
//外部公開関数
////////////////////////////////////////////////////////////
//-------------------------------
//HTMLテキストからリンクリストを返す関数
//LinkListは関数外で生成しておくこと
//返す文字列は『URL|リンク名』の形式
procedure MakeLinkList(const HtmlText: String; LinkList: TStrings;
LinkListType: TLinkList);
//-------------------------------
//タグで指定された中身を取得する関数
{タグが複数あっても先頭のFrontTag,BackTag一つしか認識しない
タグの順番が逆とか片方が片方に含まれる場合は対応外
"ABCD"などの文字列で前後タグが同じ文字列でもOK}
function GetTagText(Body, StartTag, EndTag: String): String;
begin
Result := Body;
if (AnsiIgnoreCasePos( StartTag, Result) = 0)
or (AnsiIgnoreCasePos( EndTag, Result) = 0) then
begin
Result := '';
exit;
end; {前方/後方タグどちらかが無い場合は''を戻す}
Delete( Result, 1, AnsiIgnoreCasePos(StartTag, Result) - 1 + Length(StartTag));
//前半文字を削除|先頭タグも削除
Result := copy( Result, 1, AnsiIgnoreCasePos(EndTag, Result) -1);
//タグの前にある部分を取得|後方タグも含まない
end;
//LinkListにmailtoが入っていたら削除
procedure MailToLinkDelete;
var
i: Integer;
begin
for i := LinkList.Count - 1 downto 0 do
begin
if (AnsiIgnoreCasePos('mailto:', LinkList.Strings[i]) <> 0)
or (LinkList[i] = '') then
LinkList.Delete(i);
end;
end;
//LinkListにmailtoが入っていなかったら削除
procedure MailToOtherLinkDelete;
var
i: Integer;
begin
for i := LinkList.Count - 1 downto 0 do
begin
if (AnsiIgnoreCasePos('mailto:', LinkList.Strings[i]) = 0) then
LinkList.Delete(i);
end;
end;
//http://が入っていたら削除
procedure HttpLinkDelete;
var
i: Integer;
begin
for i := LinkList.Count - 1 downto 0 do
begin
if (AnsiIgnoreCasePos('http://', LinkList.Strings[i]) <> 0)
or (LinkList[i] = '') then
LinkList.Delete(i);
end;
end;
//http://が入っていなかったら削除
procedure HttpOtherLinkDelete;
var
i: Integer;
begin
for i := LinkList.Count - 1 downto 0 do
begin
if (AnsiIgnoreCasePos('http://', LinkList.Strings[i]) = 0) then
LinkList.Delete(i);
end;
end;
//JumpLink#の指定を削除、ジャンプリンクのみのリンクなら行も削除
procedure JumpLinkDelete;
var
i: Integer;
begin
for i := LinkList.Count - 1 downto 0 do
begin
//URL#top|リンクという書式であるのか
if AnsiIgnoreCasePos('#', LinkList[i]) <> 0 then
begin
//#より左の部分がヌルかどうか
if DelimiterLeft('#', LinkList[i]) = '' then
LinkList.Delete(i)
else
LinkList[i] :=
DelimiterLeft('#', LinkList[i])
+ '|' +
DelimiterRight('|', LinkList[i]);
end;
end;
end;
var
i, LoopCount: Integer;
Str: String;
SList: TStringList;
begin
Str := StringReplace(HtmlText, #13, '', [rfReplaceAll]);
Str := StringReplace( Str, #10, '', [rfReplaceAll]);
//改行をすべて削除
Str := StringReplace(Str, '', ''#13#10, [rfReplaceAll, rfIgnoreCase]);
//''の後ろで改行
Str := StringReplace(Str, ' 0 then
Str := Str + SList[i] +#13#10;
end;
finally
SList.Free;
end;
LinkList.Text := Str;
//この時点でテキストはで終わる行ばかりになっている
//※
for i := 0 to LinkList.Count - 1 do
begin
LinkList.Strings[i] :=
GetTagText(LinkList.Strings[i], '"', '"')
+'|'+
HtmlTagDelete( LinkList.Strings[i]);
end;
//タグの中身を切り出す
//『URL』|『リンク名』という構成になる
//LinkListTypeによって処理分け
case LinkListType of
llFull:
begin
end;
llExternal:
begin
JumpLinkDelete;
MailToLinkDelete;
HttpOtherLinkDelete;
end;
llInternal:
begin
JumpLinkDelete;
MailToLinkDelete;
HttpLinkDelete;
end;
llMail:
begin
MailToOtherLinkDelete;
end;
end;
{※
TagRemain(StringReplace( Memo1.Text, #13#10, '', [rfReplaceAll]),
'', trTagCRLF);
実はこのような処理でも
で囲まれた行だけが残る事になるので
上の※で示されているまでの処理はこの一行ですんでしまう。
たぶんTagRemainを使う方が高速だし。
まあ、好きにしてくれ
}
end;
//-------------------------------
//HTMLソースから記述されている画像ファイルを取得する
{LinkListは外部で生成しておいてください。
}
procedure MakeImgList(const HtmlText: String; LinkList: TStrings;
ImgListType: TImgList);
//http://が入っていたら削除
procedure HttpLinkDelete;
var
i: Integer;
begin
for i := LinkList.Count - 1 downto 0 do
begin
if (AnsiIgnoreCasePos('http://', LinkList.Strings[i]) <> 0)
or (LinkList[i] = '') then
LinkList.Delete(i);
end;
end;
//http://が入っていなかったら削除
procedure HttpOtherLinkDelete;
var
i: Integer;
begin
for i := LinkList.Count - 1 downto 0 do
begin
if (AnsiIgnoreCasePos('http://', LinkList.Strings[i]) = 0) then
LinkList.Delete(i);
end;
end;
{↓テキストから'や"を基準に『'xx.gif'』を取り出す
Extnameは'.gif'などの形式}
procedure GetImgFilename(Extname, Str: String;
StringList1 :TStrings);
var
ExtIndex, QuotationIndex: Integer;
begin
ExtIndex := 1;
while AnsiPosRange(AnsiUpperCase(Extname), AnsiUpperCase(Str),
ExtIndex, Length(Str))<>0 do
begin
ExtIndex := AnsiPosRange(AnsiUpperCase(Extname),
AnsiUpperCase(Str),
ExtIndex, Length(Str));
QuotationIndex := Max(
BackAnsiPosRange('''', Str, 1, ExtIndex),
BackAnsiPosRange('"' , Str, 1, ExtIndex));
if QuotationIndex <> 0 then
StringList1.Add(Copy(Str, QuotationIndex+1,
(ExtIndex - QuotationIndex)+Length(Extname) - 1 ));
Inc(ExtIndex);
end;
end;
var
Str: String;
SList: TStringList;
i, LoopCount: Integer;
begin
Str := HtmlTagRemain(HtmlText, trPerfect);
{↑タグの中身だけを取り出し}
SList := TStringList.Create;
try
GetImgFilename('.gif', Str, SList);
GetImgFilename('.jpg', Str, SList);
GetImgFilename('.jpeg', Str, SList);
GetImgFilename('.png', Str, SList);
{↑これらのファイル名だけを画像としてリスト化する}
LinkList.Text := SList.Text;
finally
SList.Free;
end;
{↑これで『xx.gif』などファイル名だけが残る}
LoopCount := LinkList.Count;
for i := LoopCount - 1 downto 0 do
begin
if LinkList.IndexOf(LinkList.Strings[i]) <> i then
LinkList.Delete(i);
end; {↑ファイル名に重複項目があったら削除します}
case ImgListType of
{外部リンクと内部リンクかを区別します}
ilFull :
begin
end;
ilExternal:
begin
HttpOtherLinkDelete;
end;
ilInternal:
begin
HttpLinkDelete;
end;
end;
end;
////////////////////////////////////////////////////////////
//テキストから指定したタグで囲まれた部分を削除します
//タグは大小文字区別しません。
function TagDelete(const S, StartTag, EndTag: String): String;
var
i, TagIndex, LoopCount: Integer;
Str: String;
begin
Str := S;
Result := '';
try
{↓StartTag,EndTag,互いが互いに重複していないかをチェック}
if ( AnsiIgnoreCasePos( EndTag, StartTag) <> 0 )
or ( AnsiIgnoreCasePos( StartTag, EndTag) <> 0 ) then
raise Exception.Create('タグ不正エラー');
LoopCount := StrCount( AnsiUpperCase(StartTag), AnsiUpperCase(Str) );
//LoopCountが0でも1〜でも対応している
for i := 0 to LoopCount - 1 do
begin
TagIndex := AnsiIgnoreCasePos( StartTag, Str);
if TagIndex = 0 then
begin
Str := '';
break;
end;
Result := Result + copy( Str, 1, TagIndex - 1);
Delete(Str, 1, TagIndex - 1);
TagIndex := AnsiIgnoreCasePos( EndTag, Str);
if TagIndex = 0 then
begin
Str := '';
break;
end;
Delete(Str, 1, TagIndex + Length(EndTag) - 1);
end; //for
Result := Result + Str;
except
Result := '';
end;
end;
////////////////////////////////////////////////////////////
//HTMLテキストからタグ<>で囲まれた部分を削除します
function HTMLTagDelete(const HtmlText: String): String;
begin
Result := TagDelete( HtmlText, '<', '>');
end;
////////////////////////////////////////////////////////////
//テキストから指定したタグで囲まれた部分以外を削除します
//タグは大小文字区別しません。
{タグ自体は残ります}
function TagRemain(const S, StartTag, EndTag: String;
TagRemainFlag: TTagRemainFlag): String;
function CRLF(Value: Integer): String;
var
i: Integer;
begin
Result := '';
for i := 0 to Value - 1 do
begin
Result := Result + #13#10;
end;
end;
procedure trPerfectProc;
begin
end;
var
i, TagIndex, LoopCount: Integer;
Str: String;
begin
Str := AdjustLineBreaks(S);
Result := '';
try
if ( AnsiIgnoreCasePos( EndTag, StartTag) <> 0 )
or ( AnsiIgnoreCasePos( StartTag, EndTag) <> 0 ) then
raise Exception.Create('タグ不正エラー');
LoopCount := StrCount( AnsiUpperCase(StartTag), AnsiUpperCase(Str) );
case TagRemainFlag of
trRemainCRLF:
begin
//タグに囲まれていない部分を削除
//ただし改行コードは残しておく
for i := 0 to LoopCount - 1 do
begin
TagIndex := AnsiIgnoreCasePos( StartTag, Str);
//ここでtagIndexが0になることはありえない
if copy( Str, 1, TagIndex - 1) <> '' then
Result := Result + CRLF(StrCount( #13#10, copy( Str, 1, TagIndex - 1) ));
Delete( Str, 1, TagIndex - 1);
TagIndex := AnsiIgnoreCasePos( EndTag, Str);
if TagIndex = 0 then
begin
Result := Result + Str;
Str := '';
Break;
end;
Result := Result + copy( Str, 1, TagIndex + Length(EndTag) - 1);
Delete( Str, 1, TagIndex + Length(EndTag) - 1);
end;
DeleteExceptCRLF( Str, 1, Length(Str));
Result := Result + Str;
end;
trPerfect, trTagCRLF:
begin
//以下のコードは改行コードも含めてすべてタグに囲まれていない
//部分の文字を削除する
for i := 0 to LoopCount - 1 do
begin
TagIndex := AnsiIgnoreCasePos( StartTag, Str);
if TagIndex = 0 then
begin
break;
end;
Delete( Str, 1, TagIndex - 1);
TagIndex := AnsiIgnoreCasePos( EndTag, Str);
if TagIndex = 0 then
begin
Result := Result + Str;
break;
end;
Result := Result + copy( Str, 1, TagIndex + Length(EndTag) - 1);
Delete( Str, 1, TagIndex + Length(EndTag) - 1);
end;
end;
end; //case
case TagRemainFlag of
trTagCRLF:
begin
// Result := StringReplace( Result, EndTag, EndTag+#13#10, [rfReplaceAll, rfIgnoreCase]);
//このやり方↑ではEndTagの内容が元の内容と
//大小文字が合っていないと変なので修正しました。
Result := StringAddReplaceIgnoreAll( Result, EndTag, #13#10);
end;
end;
except
Result := '';
end;
end;
////////////////////////////////////////////////////////////
//HTMLテキストからタグ<>で囲まれた以外の部分を削除します
function HTMLTagRemain(const HtmlText: String;
TagRemainFlag: TTagRemainFlag): String;
begin
Result := TagRemain( HtmlText, '<', '>', TagRemainFlag);
end;
////////////////////////////////////////////////////////////
//URLの相対アドレスから絶対アドレスを求める
function ExtractAbsoluteURL(const BaseURL, RelativeURL: string): string;
var
i, LoopCount: Integer;
begin
if (AnsiLowerCase( copy(RelativeURL, 1, 7) ) = 'http://')
or (AnsiLowerCase( copy(RelativeURL, 1, 8) ) = 'file:///') then
begin
Result := RelativeURL;
exit;
end;
if (AnsiLowerCase( copy(BaseURL, 1, 7) ) = 'http://')
or (AnsiLowerCase( copy(BaseURL, 1, 8) ) = 'file:///') then
begin
Result := RemoveSlashMark( BaseURL );
if AnsiPos('.', DelimiterRight('/', Result)) = 0 then
//URLの最後にピリオドが含まれていないのなら
begin
Result := Result + '/a.a';
end;
//この時点でhttp://xxx〜xxx/xx.xという書式は保証されたようなもの
LoopCount := StrCount('../', RelativeURL);
for i := 0 to LoopCount - 1 + 1 do
begin
Result := DelimiterLeftLong('/', Result);
end;
//この時点でhttp://xxx〜xxxとフォルダ名になっているはず
if RelativeURL[1] = '/' then
Result := Result + RelativeURL
else
Result := Result + '/' + RelativeURL;
Result := StringReplace(Result, '../', '', [rfReplaceAll]);
Result := StringReplace(Result, './', '', [rfReplaceAll]);
end else
begin
raise Exception.Create('基本URLがURLとして認識できません');
Result := '';
end;
end;
//-------------------------------
//URLにindex.htmlをつける関数
{
http://Delphi.com
http://Delphi.com/
http://Delphi.com/index.html
→ http://Delphi.com/index.html
http://Delphi.com/Tips.html
→ そのまま
}
function AddIndexhtml(URL: String): String;
begin
Result := URL;
if AnsiPos('.', DelimiterRight('/', Result)) = 0 then
{↑URLの最後にピリオドが含まれていないのなら}
begin
PutSlashMark(URL);
Result := Result + 'index.html';
end;
end;
end.