PHP Tidy移除有效标签

4
我正在使用php扩展tidy-html来清理php输出。我知道tidy会删除无效的标签,而且甚至不能处理HTML5 doctype,但我正在使用的标签<menu>曾经出现在HTML规范中。 然而,它最终被更改为<ul>
奇怪的是,在此之前它并没有这样做。 我更改了tidy的配置,但它仍然无效。 现在我已经关闭了所有会影响标签的选项,但也没有帮助。
我的脚本相当冗长:
$tidy_config = array(
    'char-encoding' => 'utf8',
    'output-encoding' => 'utf8',
    'output-html' => true,
    'numeric-entities' => false,
    'ascii-chars' => false,
    'doctype' => 'loose',
    'clean' => false,
    'bare' => false,
    'fix-uri' => true,
    'indent' => true,
    'indent-spaces' => 2,
    'tab-size' => 2,
    'wrap-attributes' => true,
    'wrap' => 0,
    'indent-attributes' => true,
    'join-classes' => false,
    'join-styles' => false,
    'fix-bad-comments' => true,
    'fix-backslash' => true,
    'replace-color' => false,
    'wrap-asp' => false,
    'wrap-jste' => false,
    'wrap-php' => false,
    'wrap-sections' => false,
    'drop-proprietary-attributes' => false,
    'hide-comments' => false,
    'hide-endtags' => false,
    'drop-empty-paras' => true,
    'quote-ampersand' => true,
    'quote-marks' => true,
    'quote-nbsp' => true,
    'vertical-space' => true,
    'wrap-script-literals' => false,
    'tidy-mark' => true,
    'merge-divs' => false,
    'repeated-attributes' => 'keep-last',
    'break-before-br' => false
);

$tidy_config2 = array(
    'tidy-mark' => false,
    'vertical-space' => false,
    'hide-comments' => true,
    'indent-spaces' => 0,
    'tab-size' => 1,
    'wrap-attributes' => false,
    'numeric-entities' => true,
    'ascii-chars' => true,
    'hide-endtags' => true,
    'indent' => false
);
$tidy_config = array_merge($tidy_config, $tidy_config2);

$dtm = preg_match(self::doctypeMatch, $output, $dt);
$output = tidy_repair_string($output, $tidy_config, 'utf8');

// tidy screws up doctype --fixed
if($dtm)
    $output = preg_replace(self::doctypeMatch, $dt[0], $output);

$output = preg_replace('!>[\n\r]+<!', '><', $output);

unset($tidy_config);

return $output;

请注意,这比上面说的更加复杂(因此需要两个数组)。我只是删掉了不必要的代码。

你有备份可以还原吗?当你解决了这个问题,考虑将你的整洁配置存储在版本控制下。即使是 RCS 也能为你节省一份。 - Mike Sherrill 'Cat Recall'
2个回答

8
根据 W3C tidy-html5 分支 的规定,新标签的正确配置应为:
'new-blocklevel-tags' => 'article aside audio bdi canvas details dialog figcaption figure footer header hgroup main menu menuitem nav section source summary template track video',
'new-empty-tags' => 'command embed keygen source track wbr',
'new-inline-tags' => 'audio command datalist embed keygen mark menuitem meter output progress source time video wbr',

您会注意到new-blocklevel-tags中定义了一个奇怪的temp标签,用于替代旧的过时menu标签,正如@tivie在他的回答中提到的,您需要进行替换。
此外,audiovideo标签同时出现在new-blocklevel-tagsnew-inline-tags中,这改变了tidy输出HTML的方式,因为它是:
<video src="movie.webm">
<track kind="subtitles" label="English" src="subtitles.vtt" srclang="en"></video>

如果你从新的内联标签中删除video
<video src="movie.webm">
  <track kind="subtitles" label="English" src="subtitles.vtt" srclang="en">
</video>

new-blocklevel-tags 中删除 video 会产生以下结果:
<video src="movie.webm">
<track kind="subtitles" label="English" src="subtitles.vtt" srclang="en"></video>

个人而言,我更喜欢将audiovideo表现为块级标签,但这取决于您。

此外,tags.c还将command定义为CM_HEAD,将embed定义为CM_IMG。不幸的是,我不知道它们代表什么,也不认为可以模拟它们。

还有一件事:如果您没有定义new-empty-tags,则会得到奇怪的输出:

<video src="movie.webm">
  <track kind="subtitles" label="English" src="subtitles.vtt" srclang="en">
  </track>
</video>

附录

如果您还想支持WHATWG的建议,你应该添加以下标签:


这是我完整的方法:

function Tidy5($string, $options = null, $encoding = 'utf8')
{
   if (extension_loaded('tidy') === true)
   {
      $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' => false,
         '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 bdi canvas details dialog figcaption figure footer header hgroup main menu menuitem nav section source summary template track video',
         'new-empty-tags' => 'command embed keygen source track wbr',
         'new-inline-tags' => 'audio command datalist embed keygen mark menuitem meter output progress source time video 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;

      if (strpos($string, '<menu') !== false)
      {
         $menu = array
         (
            '<menu' => '<menutidy',
            '</menu' => '</menutidy',
         );
      }

      if (isset($menu) === true)
      {
         $string = str_replace(array_keys($menu), $menu, $string);
      }

      $string = tidy_repair_string($string, $options, $encoding);

      if (empty($string) !== true)
      {
         if (isset($menu) === true)
         {
            $string = str_replace($menu, array_keys($menu), $string);
         }

         if (isset($doctype) === true)
         {
            $string = $doctype . "\n" . $string;
         }

         return $string;
      }
   }

   return false;
}

+1 这个整洁的分支准备好投入生产了吗?你试过它吗? - Tivie
1
@Tivie:没有,但我在StackOverflow上一次又一次地看到它被推荐。 - Alix Axel
你知道如何防止旧版Tidy去除空标签,例如<span class="icon ..."></span>吗?我无法解决这个问题。 - Tobia
<a href=""><div>asas</div></a> ? gives <a href=""></a><div>asas</div> - Akshay Hegde

8

免责声明:

我认为我的答案不够简洁明了。这是一种使用HTMLTidy处理HTML5的方法(HTMLTidy当前不支持HTML5)。为此,我使用正则表达式解析HTML,而根据大多数人的说法,这是万恶之源或cthulhu way。如果有人知道更好的方法,请告诉我们,因为我不觉得使用正则表达式解析HTML很安全。我已经用许多例子进行了测试,但我相信它并不完美。

介绍

在HTML4和XHTML1中,menu标签被弃用,并被无序列表(ul)替代。然而,在HTML5中重新定义了这个标签,因此根据HTML5规范,它是一个有效的标签。由于HTMLTidy不支持HTML5并使用XHTML或HTML规范,正如OP所指出的那样,即使您明确告诉它不要这样做,它也会将当时已被弃用的menu标签替换为ul标签(或添加ul标签)。

我的建议

这个函数在解析前将menu标签替换为自定义标签。然后再将自定义标签替换为menu标签。

function tidyHTML5($buffer)
{
    $buffer = str_replace('<menu', '<mytag', $buffer);
    $buffer = str_replace('menu>', 'mytag>', $buffer);
    $tidy = new tidy();
    $options = array(
            'hide-comments'         => true,
            'tidy-mark'             => false,
            'indent'                => true,
            'indent-spaces'         => 4,
            'new-blocklevel-tags'   => 'menu,mytag,article,header,footer,section,nav',
            'new-inline-tags'       => 'video,audio,canvas,ruby,rt,rp',
            'doctype'               => '<!DOCTYPE HTML>',
            //'sort-attributes'     => 'alpha',
            'vertical-space'        => false,
            'output-xhtml'          => true,
            'wrap'                  => 180,
            'wrap-attributes'       => false,
            'break-before-br'       => false,
            'char-encoding'         => 'utf8',
            'input-encoding'        => 'utf8',
            'output-encoding'       => 'utf8'
    );

    $tidy->parseString($buffer, $options, 'utf8');
    $tidy->cleanRepair();

    $html = '<!DOCTYPE HTML>' . PHP_EOL . $tidy->html();
    $html = str_replace('<html lang="en" xmlns="http://www.w3.org/1999/xhtml">', '<html>', $html);
    $html = str_replace('<html xmlns="http://www.w3.org/1999/xhtml">', '<html>', $html);

    //Hackish stuff starts here
    //We use regex to parse html, which is usually a bad idea
    //But currently there is no alternative to it, since tidy is not MENU TAG friendly
    preg_match_all('/\<mytag(?:[^\>]*)\>\s*\<ul>/', $html, $matches);
    foreach($matches as $m) {
        $mo = $m;
        $m = str_replace('mytag', 'menu', $m);
        $m = str_replace('<ul>', '', $m);
        $html = str_replace($mo, $m, $html);
    }
    $html = str_replace('<mytag', '<menu', $html);
    $html = str_replace('</ul></mytag>', '</menu>', $html);
    $html = str_replace('mytag>', 'menu>', $html);
    return $html;
}

测试:

header("Content-type: text/plain");
echo tidyHTML5('<menu><li>Lorem ipsum</li></menu><div></div><menu   ><a href="#">lala</a><form id="jj"><button>btn</button></form></menu><menu style="color: white" id="nhecos"><li>blabla</li><li>sdfsdfsdf</li></menu>');

输出:

<!DOCTYPE HTML>
<html>
    <head>
        <title></title>
    </head>
    <body>
        <menu>

            <li>Lorem ipsum
            </li>
        </menu><menu style="color: white" id="nhecos">

            <li>blabla
            </li>
            <li>sdfsdfsdf
            </li>
        </menu>
    </body>
</html>

+1,非常不错的答案,可以看看我的答案,可能有所改进。另外,我发现将 doctype 设置为 omit,然后加上前缀比设置为 HTML5 更可靠。还有一个问题,为什么要使用 output-xhtml 而不是 output-html - Alix Axel
哦,我刚刚读了你代码的其余部分... 为什么不在整理之前就做 $buffer = str_replace(array('<menu', '</menu'), array('<temp', '</temp'), $buffer); 然后在整理之后做 $buffer = str_replace(array('<temp', '</temp'), array('<menu', '</menu'), $buffer);?我不明白为什么需要 preg_match_all 和所有的 ul 替换。 - Alix Axel
@AlixAxel,HTML不支持(根据定义)XML标签(即具有“自定义”名称的标签)。你提到的所有冗余代码都是为了捕获所有可能的组合,以便将mytag替换为menu。例如,“<menu>”是有效的HTML,并且不会被你的代码替换。此外,你可以使用命名空间menu“namespace:menu”。 - Tivie
@AlixAxel 嗯...实际上,我认为整理自己的HTML没有太大意义。我通常在消耗来自互联网某处的公共HTML源码时使用tidy工具,以便更轻松地解析它。 - Tivie
@Tivie:我也是这样,但显然这就是 OP 正在做的事情,有时我也不想暴露我的部分内容(它们会被取消缩进),而通过 preg_replace 删除所有空格会破坏类似于 pre 的标签。 - Alix Axel
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接