2

免責事項:この質問の長さをむき出しにしてください。これは、明確で実用的な解決策が提示されていないのに、何百回も尋ねられてきた実世界の問題に対する繰り返しの質問です。

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_replacestr_ireplacestr_replace

そこで私は に目を向けましたが、 が働くDOMDocumentためには次のことをしなければならないことがすぐにわかりました。formatOutput

  1. タグ間のすべての空白を取り除きます (もちろん正規表現を使用します: '~>[[:space:]]++<~m'> ><)
  2. たとえば、すべての改行の組み合わせをに変換して、エンコードし\nないようにします\r&#23;
  3. 入力文字列を 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[]]>scriptstylepreg_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-html5tidy - HTML5 タグ/属性をサポートすることを除いて、通常と同じ問題

この時点で、より良い解決策が存在しない場合、正規表現でのみ機能するものを作成することを検討しています。しかし、カスタム XSLT を使用するDOMDocumentことで、HTML5 とscript/styleタグを強制的に使用できるのではないかと考えました。私はこれまで XSLT をいじったことがないので、これが現実的かどうかはわかりませんが、おそらく XML の専門家の 1 人が教えてくれ、出発点を提供してくれるでしょう。

4

2 に答える 2

1

HTML 出力をデバッグする場合など、ページを本番用に変換するのか、開発用に変換するのかについては言及していません。

後者の場合、正規表現ベースのソリューションを作成することについて既に言及しているので、その目的のためにDindentを作成しました。

入力のサンプルと期待される出力が含まれていません。サンドボックスを使用して実装をテストできます。

于 2014-02-22T12:41:52.967 に答える