(*----------------------------------------
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関数なども含めました。
2011/06/24(金)
・ TagDeleteを改良しTagReplaceも作成しました。
//----------------------------------------*)
unit HtmlTxtCtrl;
interface
uses
SysUtils, Classes, Dialogs, Math,
StringUnit,
StringSearchUnit,
uses_end;
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;
CaseCompare: TCaseCompare = ccIgnoreCase): String;
//・テキストから任意のタグで囲まれた部分を削除
function TagReplace(const S, StartTag, EndTag, ReplaceString: String;
CaseCompare: TCaseCompare = ccIgnoreCase): 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 : String; index, count : Integer);
var
i, CRLFCount: Integer;
begin
CRLFCount := StringCount(#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 := StringCount( SearchPattern, S, scfIncSubStr, ccIgnoreCase);
for i := 0 to LoopCount - 1 do
begin
FindIndex := PosForward(SearchPattern, Str, ccIgnoreCase);
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 (PosForward( StartTag, Result, ccIgnoreCase) = 0)
or (PosForward( EndTag, Result, ccIgnoreCase) = 0) then
begin
Result := '';
exit;
end; {前方/後方タグどちらかが無い場合は''を戻す}
Delete( Result, 1, PosForward(StartTag, Result, ccIgnoreCase) - 1 + Length(StartTag));
//前半文字を削除|先頭タグも削除
Result := copy( Result, 1, PosForward(EndTag, Result, ccIgnoreCase) -1);
//タグの前にある部分を取得|後方タグも含まない
end;
//LinkListにmailtoが入っていたら削除
procedure MailToLinkDelete;
var
i: Integer;
begin
for i := LinkList.Count - 1 downto 0 do
begin
if (PosForward('mailto:', LinkList.Strings[i], ccIgnoreCase) <> 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 (PosForward('mailto:', LinkList.Strings[i], ccIgnoreCase) = 0) then
LinkList.Delete(i);
end;
end;
//http://が入っていたら削除
procedure HttpLinkDelete;
var
i: Integer;
begin
for i := LinkList.Count - 1 downto 0 do
begin
if (PosForward('http://', LinkList.Strings[i], ccIgnoreCase) <> 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 (PosForward('http://', LinkList.Strings[i], ccIgnoreCase) = 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 PosForward('#', LinkList[i], ccIgnoreCase) <> 0 then
begin
//#より左の部分がヌルかどうか
// if DelimiterLeft('#', LinkList[i]) = '' then
if FirstString(LinkList[I], '#') = '' then
LinkList.Delete(i)
else
// LinkList[i] :=
// DelimiterLeft('#', LinkList[i])
// + '|' +
// DelimiterRight('|', LinkList[i]);
LinkList[I] :=
FirstString(LinkList[I], '#')
+ '|' +
LastString(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 (PosForward('http://', LinkList.Strings[i], ccIgnoreCase) <> 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 (PosForward('http://', LinkList.Strings[i], ccIgnoreCase) = 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));
{↑↓関数を置き換えた}
while PosForward(UpperCase(Extname), UpperCase(Str),
ExtIndex, Length(Str))<>0 do
begin
ExtIndex := PosForward(UpperCase(Extname),
UpperCase(Str),
ExtIndex, Length(Str));
QuotationIndex := Max(
PosBackward('''', Str, 1, ExtIndex),
PosBackward('"' , 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;
// CaseCompare: TCaseCompare = ccIgnoreCase): String;
//var
// i, TagIndex, LoopCount: Integer;
// Str: String;
//begin
// Str := S;
// Result := '';
//
// try
// {↓StartTag,EndTag,互いが互いに重複していないかをチェック}
// if ( PosForward( EndTag, StartTag, CaseCompare) <> 0 )
// or ( PosForward( StartTag, EndTag, CaseCompare) <> 0 ) then
// raise Exception.Create('タグ不正エラー');
//
// LoopCount := StringCount(StartTag, Str, scfIncSubStr, CaseCompare);
//
// //LoopCountが0でも1〜でも対応している
// for i := 0 to LoopCount - 1 do
// begin
// TagIndex := PosForward( StartTag, Str, CaseCompare);
// if TagIndex = 0 then
// begin
// Str := '';
// break;
// end;
// Result := Result + copy( Str, 1, TagIndex - 1);
// Delete(Str, 1, TagIndex - 1);
//
// TagIndex := PosForward( EndTag, Str, CaseCompare);
// if TagIndex = 0 then
// begin
// Str := '';
// break;
// end;
// Delete(Str, 1, TagIndex + Length(EndTag) - 1);
// end; //for
// Result := Result + Str;
// except
// Result := '';
// end;
//end;
function TagDelete(const S, StartTag, EndTag: String;
CaseCompare: TCaseCompare = ccIgnoreCase): String;
begin
Result := TagReplace(S, StartTag, EndTag, '', CaseCompare);
end;
function TagReplace(const S, StartTag, EndTag, ReplaceString: String;
CaseCompare: TCaseCompare = ccIgnoreCase): String;
var
i, TagIndex, LoopCount: Integer;
Str: String;
begin
Str := S;
Result := '';
try
{↓StartTag,EndTag,互いが互いに重複していないかをチェック}
if ( PosForward( EndTag, StartTag, CaseCompare) <> 0 )
or ( PosForward( StartTag, EndTag, CaseCompare) <> 0 ) then
raise Exception.Create('タグ不正エラー');
LoopCount := StringCount(StartTag, Str, scfIncSubStr, CaseCompare);
//LoopCountが0でも1〜でも対応している
for i := 0 to LoopCount - 1 do
begin
TagIndex := PosForward( StartTag, Str, CaseCompare);
if TagIndex = 0 then
begin
Str := '';
break;
end;
Result := Result + copy( Str, 1, TagIndex - 1);
Delete(Str, 1, TagIndex - 1);
Result := Result + ReplaceString;
TagIndex := PosForward( EndTag, Str, CaseCompare);
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 ( PosForward( EndTag, StartTag, ccIgnoreCase) <> 0 )
or ( PosForward( StartTag, EndTag, ccIgnoreCase) <> 0 ) then
raise Exception.Create('タグ不正エラー');
LoopCount := StringCount( StartTag, Str, scfIncSubStr, ccIgnoreCase );
case TagRemainFlag of
trRemainCRLF:
begin
//タグに囲まれていない部分を削除
//ただし改行コードは残しておく
for i := 0 to LoopCount - 1 do
begin
TagIndex := PosForward( StartTag, Str, ccIgnoreCase);
//ここでtagIndexが0になることはありえない
if copy( Str, 1, TagIndex - 1) <> '' then
Result := Result + CRLF(StringCount( #13#10, copy( Str, 1, TagIndex - 1), scfIncSubStr, ));
Delete( Str, 1, TagIndex - 1);
TagIndex := PosForward( EndTag, Str, ccIgnoreCase);
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 := PosForward( StartTag, Str, ccIgnoreCase);
if TagIndex = 0 then
begin
break;
end;
Delete( Str, 1, TagIndex - 1);
TagIndex := PosForward( EndTag, Str, ccIgnoreCase);
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
if Pos('.', LastString(Result, '/')) = 0 then
//URLの最後にピリオドが含まれていないのなら
begin
Result := Result + '/a.a';
end;
//この時点でhttp://xxx〜xxx/xx.xという書式は保証されたようなもの
LoopCount := StringCount('../', RelativeURL, scfIncSubStr);
for i := 0 to LoopCount - 1 + 1 do
begin
// Result := DelimiterLeftLong('/', Result);
Result := FirstStringLong(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
if Pos('.', LastString(Result, '/')) = 0 then
{↑URLの最後にピリオドが含まれていないのなら}
begin
PutSlashMark(URL);
Result := Result + 'index.html';
end;
end;
end.