4D-jp 4D Japan Technical Support Team

特定の文字のパターンを抽出する方法(その2)

2023-03-08

前の記事では、正規表現を用いて特定のパターンの文字列を抽出しましたが、今回は特定のパターンの文字列を抽出して、それを元に削除する方法について考えてみます。

目標

例題では、HTMLファイルのBODY部を抽出して、そのタグを全て消去することを目指します。

XMLコマンドでも同様のことが可能ですが、XMLとは違いHTMLはパースに失敗するようなソースも多いので、正規表現を用いて行うのが非常に有用です。 またXMLでパースに失敗するような不完全なXMLデータも、正規表現を用いることで必要な情報を抜き出すことも可能でしょう。

例題HTML

例題で使うHTMLは、4DのWebサーバー機能を有効にしたときに生成されるファイルを元にしたものを利用しました。 少し、ハードルを高くするため、次のように手を入れました。

  1. コメント行の文章中に<がある
  2. <の数が>よりも多い
  3. 複数行に亘るタグがある

HTMLのソースコードは次のとおりです。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>4D Application Webサーバーへようこそ</title>
<style type="text/css">
<!--
h2 {
font: 16px "ヒラギノ角ゴ Pro W3", "メイリオ", "MS Pゴシック", Verdana, Arial, Helvetica, sans-serif;
}
p {
font: 12px "ヒラギノ角ゴ Pro W3", "メイリオ", "MS Pゴシック", Verdana, Arial, Helvetica, sans-serif;
}
-->
</style>
</head>
<body>
<!--
ここにオリジナルには無いコメントがありますが、その中に"<"があるところに注目してください
コメント中の"<"や">"があっても正規表現でスパッと解決です
-->
<div align="center">
<table
	border="0"
	cellpadding="0"
	cellspacing="0"
	width="675"
><!--複数の行にまたがる1つのタグにも注目してください-->
<tr>
<td> 
<h2 align="center">4D Webサーバーのデフォルトホームページへようこそ!</h2>
<p align="center">これは<strong><b>4D Webサーバー</b></strong> のデフォルトホームページです。<br>この<strong>テストページ</strong>は4Dアプリケーションから送信されています。</p>
<p align="center">Webマスターの方、おめでとうございます!<br>あなたのWebサーバーは正常に起動されました。<br>あとはデフォルトの &quot;index.html&quot; ファイルをあなたのページと入れ替えてください。</p>
<p align="center">4D Webサーバーを設定する方法については、マニュアルをご覧ください。</p>
<p align="center"><b>重要</b>: このWebページやWebサイトは、4D SASやその子会社によって所有または管理されるものではありません。このサイトに関するお問い合わせは、サイトの所有者またはWebマスタにお願いいたします。</p>
<p align="center">&copy;1995-2022 4D, Inc., 4D SAS and its Licensors.<br>All rights reserved.</p>
</td>
</tr>
</table>
</div>
</body>
</html>

正規表現でタグを削除

いよいよ本題のコードです。

正規表現式は複数行を一度に評価するように書いてあります。 複数行で評価させるポイントは…

  1. (?m)で複数行を扱うことを宣言(注:このケースでは無くても大丈夫)
  2. 任意文字を表すのに[\s\S]を使う

任意文字を通常よく使う.で表現していまいますと、改行コードなどが含まれていないので、複数行の評価では上手く働かない箇所が生まれてしまいます。 そこで、\s\Sを組み合わせて、任意文字として利用しています。

記号 意味 備考
\s すべての空白文字 半角スペース、\t、\n、\r、\f
\S 空白文字以外のすべて 上記以外

このように[\s\S]を使うことで、コード全体がシンプルにまとまります。 実際のコードは次のとおりです。

//データファイルの位置を基点にしてファイル選択を促す
$name:=Select document(Folder("/DATA/").platformPath; "html;htm"; "HTMLファイルを選択してください"; Allow alias files)

If (OK=1)
	
	//対象のファイルを読み込む
	$htmlfile:=File(Document; fk platform path)
	$html:=$htmlfile.getText()
	
	//正規表現式での評価の結果を配列で受け取るようにする
	ARRAY LONGINT($pos; 0)
	ARRAY LONGINT($len; 0)
	
	//ヘッダ部を削除
	If (Match regex("<body"; $html; 1; $pos; $len))
		$html:=Substring($html; $pos{0})
	End if 
	
	//最初にコメントを削除
	While (Match regex("(?m)(<!--[\\s\\S]*?-->)"; $html; 1; $pos; $len))
		$html:=Substring($html; 1; $pos{1}-1)+Substring($html; $pos{1}+$len{1})
	End while 
	
	//タグを削除
	While (Match regex("(?m)(<[\\s\\S]+?>)"; $html; 1; $pos; $len))
		$html:=Substring($html; 1; $pos{1}-1)+Substring($html; $pos{1}+$len{1})
	End while 
	
	//拡張子をテキストに変更して保存
	$newfile:=File($htmlfile.parent.path+$htmlfile.name+".txt").setText($html)
	
End if 

コードの解説

見ていただければ、きっとご理解いただけると思いますが、一応解説をしておきます。

ヘッダ部を最初に削除していますが、bodyタグを見つけて、bodyタグ先頭からのテキストを切り出すことで実現しています。 言い換えるなら「ボディ部だけにする」といったところでしょうか。 他の正規表現式ではグループを用いていますが、この式だけはグループを使っていませんので、$posを参照するのに0番目を利用しています。

次にコメントを削除していますが、コメントにはタグの残りカスのようなものが含まれていることが多く、それらが後々悪影響を及ぼすことがあるので、最初にコメントを削除しています。 コメントの長さも重要な情報なので、ここでの式にはカッコで括られたグループを用いていた式になっています。 (<!--[\\s\\S]*?-->)が意味するのは、<!--から始まり-->で終わる文字列ですが、[\\s\\S]*?と書かれた部分は、改行などのコードも含めたすべての文字列を等価であると評価されるので、コメントの位置と長さの両方を一度に得ることができます。

[\\s\\S]*?については、次の式でも利用していますが、記号*は記号の直前の文字が0以上の長さを持った文字列であることを意味し、続いて付けた記号?によって、できるだけ短い文字列で評価する式となります。 もし記号?が無いと、できるだけ長い文字列として評価されるので、最初のコメントから最後のコメントまでが連続して等価となってしまい、タグではない残したい文字列まで含まれてしまうことになります。 この例題では記号?はとても重要です。

最後にメインのタグを削除するところですが、例題のHTMLのtableタグが複数の行にまたがる記述になっていても問題なく評価されている点に注目してください。 正規表現式に関しましては、基本的にコメントを削除するときの式と同じなので、説明は省かせてもらいます。

実行結果

実際に、例題を実行した結果、HTMLのボディ部がテキストとして取り出せました。

抽出されたテキストを見ますと、前後に余計な空白行や文字参照がありますが、タグに関してはきれいに取り除かれていることが分かります。 空白行が不要な場合も、文字参照を文字に変換するのにも、この例題のテクニックを応用すると、すっきりとしたコードで実現することが可能です。 色々と研究していただければと思います。






 
4D Webサーバーのデフォルトホームページへようこそ!
これは4D Webサーバー のデフォルトホームページです。このテストページは4Dアプリケーションから送信されています。
Webマスターの方、おめでとうございます!あなたのWebサーバーは正常に起動されました。あとはデフォルトの &quot;index.html&quot; ファイルをあなたのページと入れ替えてください。
4D Webサーバーを設定する方法については、マニュアルをご覧ください。
重要: このWebページやWebサイトは、4D SASやその子会社によって所有または管理されるものではありません。このサイトに関するお問い合わせは、サイトの所有者またはWebマスタにお願いいたします。
&copy;1995-2022 4D, Inc., 4D SAS and its Licensors.All rights reserved.







まとめ

正規表現式は奥の深いものですので、文字を扱うコードが何やら複雑になってしまったようなときには、正規表現式で評価することを検討してみてください。

おまけ

ひな形として例題に使いました実際の4DのIndex.htmlファイルには、HTMLの数値文字参照が用いられています。 数値文字参照は文字化けを防ぐ有効な方法ですが、この記事を書くに当たり、HTMLコードを読みやすくする目的で数値文字参照を文字に変換したソースを用いています。 数値文字参照を文字に変換するために用いたコードを最後に付記しておきますので、ご参考になると幸いです。

//データファイルの位置を基点にしてファイル選択を促す
$name:=Select document(Folder("/DATA/").platformPath; "html;htm"; "HTMLファイルを選択してください"; Allow alias files)

If (OK=1)
	
	//対象のファイルを読み込む
	$htmlfile:=File(Document; fk platform path)
	$text:=$htmlfile.getText()
	
	//正規表現式での評価の結果を配列で受け取るようにする
	ARRAY LONGINT($pos; 0)
	ARRAY LONGINT($len; 0)
	
	//すべての数値文字参照を文字に変換
	While (Match regex("&#x([0-9A-F]+);"; $text; 1; $pos; $len))
		$charcode:=Substring($text; $pos{1}; $len{1})
		$code:=OB Get(New object("num"; "0x"+$charcode); "num"; Is longint)  //オブジェクト型変数を経由して倍長整数で取り出すことで16->10進数に変換
		$char:=Char($code)
		$text:=Replace string($text; "&#x"+$charcode+";"; $char)
	End while 
	
	//新しいファイルとして書き出す
	$newfile:=File($htmlfile.parent.path+$htmlfile.name+"-2"+$htmlfile.extension).setText($text)
	
End if