免責事項:この質問の長さをむき出しにしてください。これは、明確で実用的な解決策が提示されていないのに、何百回も尋ねられてきた実世界の問題に対する繰り返しの質問です。
PHP を使用して一括インデントしたい HTML ファイルが何百もあります。最初は Tidy を使用することを考えていましたが、ご存知のように、既定では HTML5 のタグと属性と互換性がありません。いくつかの調査とさらに多くのテストの後、HTML 5 サポートを「偽装」する次の実装を思いつきました。
function Tidy5($string, $options = null, $encoding = 'utf8')
{
$tags = array();
$default = array
(
'anchor-as-name' => false,
'break-before-br' => true,
'char-encoding' => $encoding,
'decorate-inferred-ul' => false,
'doctype' => 'omit',
'drop-empty-paras' => false,
'drop-font-tags' => true,
'drop-proprietary-attributes' => false,
'force-output' => true,
'hide-comments' => false,
'indent' => true,
'indent-attributes' => false,
'indent-spaces' => 2,
'input-encoding' => $encoding,
'join-styles' => false,
'logical-emphasis' => false,
'merge-divs' => false,
'merge-spans' => false,
'new-blocklevel-tags' => ' article aside audio details dialog figcaption figure footer header hgroup menutidy nav section source summary track video',
'new-empty-tags' => 'command embed keygen source track wbr',
'new-inline-tags' => 'btidy canvas command data datalist embed itidy keygen mark meter output progress time wbr',
'newline' => 0,
'numeric-entities' => false,
'output-bom' => false,
'output-encoding' => $encoding,
'output-html' => true,
'preserve-entities' => true,
'quiet' => true,
'quote-ampersand' => true,
'quote-marks' => false,
'repeated-attributes' => 1,
'show-body-only' => true,
'show-warnings' => false,
'sort-attributes' => 1,
'tab-size' => 4,
'tidy-mark' => false,
'vertical-space' => true,
'wrap' => 0,
);
$doctype = $menu = null;
if ((strncasecmp($string, '<!DOCTYPE', 9) === 0) || (strncasecmp($string, '<html', 5) === 0))
{
$doctype = '<!DOCTYPE html>'; $options['show-body-only'] = false;
}
$options = (is_array($options) === true) ? array_merge($default, $options) : $default;
foreach (array('b', 'i', 'menu') as $tag)
{
if (strpos($string, '<' . $tag . ' ') !== false)
{
$tags[$tag] = array
(
'<' . $tag . ' ' => '<' . $tag . 'tidy ',
'</' . $tag . '>' => '</' . $tag . 'tidy>',
);
$string = str_replace(array_keys($tags[$tag]), $tags[$tag], $string);
}
}
$string = tidy_repair_string($string, $options, $encoding);
if (empty($string) !== true)
{
foreach ($tags as $tag)
{
$string = str_replace($tag, array_keys($tag), $string);
}
if (isset($doctype) === true)
{
$string = $doctype . "\n" . $string;
}
return $string;
}
return false;
}
script
動作しますが、HTML コメントとstyle
タグが正しくインデントされていないという2 つの欠点があります。
<link href="/_/style/form.css" rel="stylesheet" type="text/css"><!--[if lt IE 9]>
<script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<!--<script type="text/javascript" src="//raw.github.com/kevinburke/tecate/master/tecate.js"></script>-->
</script><script charset="UTF-8" src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.0.0/js/locales/bootstrap-datepicker.pt.js" type="text/javascript">
</script><!--<script src="/3rd/parsley/i18n/messages.pt_br.js"></script>-->
<!--<script src="//cdnjs.cloudflare.com/ajax/libs/parsley.js/1.1.10/parsley.min.js"></script>-->
<script src="/3rd/select2/locales/select2_locale_pt-PT.js" type="text/javascript">
</script><script src="/3rd/tcrosen/bootstrap-typeahead.js" type="text/javascript">
もう 1 つの欠陥は、はるかに重大です。Tidy はすべてのmenu
タグを に変換し、空のinline タグul
を削除することを主張するため、ハックして回避する必要があります。それを完全に明確にするために、いくつかの例を次に示します。
<br>
空のタグ<i>text</i>
インラインタグ<i class="icon-home"></i>
空のインライン タグ (Font Awesome の例)
コードを調べると、完全ではないハックを使用してb
、 、i
およびmenu
タグを説明していることに気付くでしょう。より堅牢な正規表現を使用したり、同じことを達成したりすることもできましたが、私の目的では、より高速で十分です。ただし、それでは、説明していない他の空のインライン タグが残ります。これは残念です。 str_replace
str_ireplace
str_replace
そこで私は に目を向けましたが、 が働くDOMDocument
ためには次のことをしなければならないことがすぐにわかりました。formatOutput
- タグ間のすべての空白を取り除きます (もちろん正規表現を使用します:
'~>[[:space:]]++<~m'
>><
) - たとえば、すべての改行の組み合わせをに変換して、エンコードし
\n
ないようにします\r

- 入力文字列を HTML として読み込み、XML として出力します
驚いたことに、DOMDocument にも空のインライン タグに関する問題があります。基本的に、それが<i class="icon-home"></i><someOtherTag>text</someOtherTag>
表示されるか類似するたび<i class="icon-home"><someOtherTag>text</someOtherTag></i>
に、ページのブラウザ レンダリングが完全に台無しになります。LIBXML_NOEMPTYTAG
それを克服するために、 withを使用するDOMDocument::saveXML()
と、コンテンツのないタグ ( などの完全に空のタグを含む<br />
) がインラインの終了タグに変わることがわかりました。たとえば、次のようになります。
<i class="icon-home"></i>
同じままです(そうあるべきです)<br>
ブラウザのレンダリングが<br></br>
台無しになります(またしても)
これを修正するには、正規表現を使用し~></(?:area|base(?:font)?|br|col|command|embed|frame|hr|img|input|keygen|link|meta|param|source|track|wbr)>~
て一致した文字列を検索し、単純な/>
. もう 1 つの大きな問題は、HTMLと内部の HTML の周りに..ブロックsaveXML()
が追加されることです。これにより、それらのコンテンツが無効になり、それらのトークンに戻って再度それらのトークンに戻る必要があります。これは「機能します」:<![CDATA[
]]>
script
style
preg_replace
function DOM5($html)
{
$dom = new \DOMDocument();
if (libxml_use_internal_errors(true) === true)
{
libxml_clear_errors();
}
$html = mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8');
$html = preg_replace(array('~\R~u', '~>[[:space:]]++<~m'), array("\n", '><'), $html);
if ((empty($html) !== true) && ($dom->loadHTML($html) === true))
{
$dom->formatOutput = true;
if (($html = $dom->saveXML($dom->documentElement, LIBXML_NOEMPTYTAG)) !== false)
{
$regex = array
(
'~' . preg_quote('<![CDATA[', '~') . '~' => '',
'~' . preg_quote(']]>', '~') . '~' => '',
'~></(?:area|base(?:font)?|br|col|command|embed|frame|hr|img|input|keygen|link|meta|param|source|track|wbr)>~' => ' />',
);
return '<!DOCTYPE html>' . "\n" . preg_replace(array_keys($regex), $regex, $html);
}
}
return false;
}
HTML をインデントする最も推奨され、検証されている 2 つの方法は、実際のHTML5に対して正しい結果または信頼できる結果を生成しないようです。
次のような他のライブラリを試しました。
- html5lib - 動作しませんでし
DOMDocument::$formatOutput
た - tidy-html5
tidy
- HTML5 タグ/属性をサポートすることを除いて、通常と同じ問題
この時点で、より良い解決策が存在しない場合、正規表現でのみ機能するものを作成することを検討しています。しかし、カスタム XSLT を使用するDOMDocument
ことで、HTML5 とscript
/style
タグを強制的に使用できるのではないかと考えました。私はこれまで XSLT をいじったことがないので、これが現実的かどうかはわかりませんが、おそらく XML の専門家の 1 人が教えてくれ、出発点を提供してくれるでしょう。