难题:正确分割HTML字符串

5
我想将一个HTML字符串通过标记进行分割,以创建博客预览而不显示完整帖子。这比我最初想象的要 困难 一些。以下是问题:
  • 用户将通过所见即所得编辑器(CKEditor)创建HTML,因此标记可能不美观或不一致。
  • 标记read_more()可以放置在字符串中的任何位置,包括嵌套在段落标签内。
  • 第一个分割字符串需要对标记的所有合理用途都是有效的HTML。

可能使用的示例:

<p>Some text here. read_more()</p>

<p>Some text read more() here.</p>

<p>read_more()</p>

<p>  read_more()</p>

read_more()

到目前为止,我只是尝试在标记上拆分字符串,但这会留下无效的HTML。正则表达式可能是另一种选择。您会使用什么策略来解决这个问题,并使其尽可能坚不可摧?如果有任何代码片段或提示,也将不胜感激(我正在使用PHP)。


7
正则表达式不是一个选择。请参考另一个SO问题的答案:https://dev59.com/X3I-5IYBdhLWcg3wq6do#1732454 - You
为什么不能在结果字符串上使用trim(),找到缺失的开放或关闭元素并适当地附加它,以使其成为有效的HTML? - James Black
如果正则表达式不可行,请随意建议另一个选项,适用于可能无效的(X)HTML。据我所知,PHP没有一个XML解析器,它不会在无效的XML上抛出错误并且不是GPL许可证。 - VirtuosiMedia
@James Black - 这可能是一个选项,但是找到缺失的闭合标签的最佳方法是什么? - VirtuosiMedia
1
DOMDocument::loadHTML() 怎么样? - MooGoo
6个回答

2
function stripmore($in)
{
    list($p1,$p2) = explode("read_more()",$in,2);

    $pass1 = preg_replace("~>[^<>]+<~","><",$p2);
    $pass2 = preg_replace("~^[^<>]+~","",$pass1);

    $pass3 = null;
    while ( $pass3 != $pass2 )
    {
        if ( $pass3 !== null ) $pass2 = $pass3;
        $pass3 = preg_replace("~<([^<>]+)></\\1>~","",$pass2);
    }

    return $p1."read_more()".$pass3;
}

这个函数会在“read_more()”标记之后去除任何非HTML内容,并通过去除相应的标签将其最小化,同时保留在标记之前开始且在标记之后结束的任何标签:

<p>Some text here. read_more()</p>
      ==> <p>Some text here. read_more()</p>

<p>Some <b>text</b> read_more() <b>here</b>.</p>
      ==> <p>Some <b>text</b> read_more()</p>

<p>Some <b>text read_more() here</b>.</p>
      ==> <p>Some <b>text read_more()</b></p>

谢谢,mvds,这个很好用。如果可以的话,我能使用你的函数吗?如果可以,你希望在代码中如何被署名? - VirtuosiMedia
您可以根据需要使用它,至于信用,请尽量不透露。另外,您还需要去掉“〜[^<>]+$〜”(最后一个标签后面的所有内容),也许还有像“〜<img[^<>]*>〜”这样的标签。 - mvds

1

PHP tidy 是一个非常轻量级和高效的工具,用于修复无效的标签。 看一下吧,我在我的应用程序中使用它并对其进行了基准测试,它运行得很好。 此外,它有许多配置选项,可以最好地满足您的需求,并处理其他可能的问题,如编码、嵌套的无效标记等。

请参阅参考: http://www.php.net/manual/en/tidy.cleanrepair.php

示例用法:

<?php

    function tidyString($str)
    {
      $config = array('show-body-only' => true); /* else it adds HTML tags too */
      tidy_set_encoding('utf8');
      $outStr = tidy_repair_string($str,$config);
      return $outStr;
    }


    $inStr = "<span> this is my incorrect html</spa";
    echo tidyString($inStr);  // Output : <span>this is my incorrect html</span>

    ?>

1

目前我唯一看到的正确选项是在PHP中编写自己的上下文无关语法HTML解析器,这将允许您适当地关闭标签(只需在达到read more()时弹出堆栈,并为每个弹出添加一个闭合标签即可)。



然而,这是很多工作,以下可能适合您:

$stripped = strip_tags($input);
list($preview) = explode("read more()", $stripped);

你会失去HTML标记,但实现起来非常简单。并且你的前端页面不会有任何可能的XSS攻击 :)


失去HTML标记不是一个选择,但感谢您的建议。 - VirtuosiMedia
+1 对于第一段关于编写解析器的内容 - 这就是我为自己的博客所做的。它基本上从开头开始遍历文本,并保持当前打开的 HTML 标记的堆栈,然后一旦确定在哪里断开文本,它就会附加必要的任何关闭标记。我的代码有点复杂,因为我没有明确的标记来标记分割 - 而且它是用 Python 编写的 - 但如果您愿意,我很乐意分享代码。 - David Z

1

不要使用完整的HTML,为什么不使用许多可以生成HTML但不需要关闭标签等的标记语言之一呢?这样更容易培训用户,并避免接受原始HTML带来的所有可能的XSS攻击。

PHP Markdown似乎是一个明显的选择,特别是考虑到您想避免GNU GPL的情况。


这是为CMS的管理员部分设计的,因此我希望尽可能少地学习曲线。我选择CKEditor,因为它比Markdown编辑器更具功能性,并且它允许非技术用户接近于Word的操作。我正在过滤输入。感谢您的建议。 - VirtuosiMedia
那么,鉴于WordPress、Drupal、Joomla和其他许多开源内容管理系统的可用性,为什么你还要编写另一个呢?只是好奇。 - Craig Trader

1
为了回答我的评论,我决定将其作为答案,这样我就可以利用标记选项。
为什么不能在结果字符串上使用trim(),找到缺失的开放或关闭元素并适当地附加它,使其成为有效的HTML?
只需向前和向后遍历以查找下一个打开/关闭元素,并修复您的HTML。
因此,您可以在字符串中向前和向后走来获取下一个<>,如果那是一个HTML元素,则停在那里,否则继续前进。
理想情况下,您应该每次提交时都需要处理一次,这样您就需要付出代价来执行此操作。
更新:
我忘记包含一个帮助strpos的链接:

http://tuxradar.com/practicalphp/4/7/5


0

为什么不使用两个文本区域?一个在剪切上方,另一个在下方?这应该让用户明白正在发生什么,并消除您的头痛。

如果您确实想使用令牌,那么您应该选择更具有特色的内容。也许是:<!--full body cut-->,您可以更加确定它不是被误认为是令牌的内容。

无论如何,如果您想要在令牌上拆分字符串,您只需要使用 strpos() 找出您的令牌位置,然后使用 substr() 去掉第一部分。就像这样:

$intro = substr($text, 0, strpos($string, $token));

接下来,运行你的$intro通过tidy(PHP扩展)来清理语法,然后剥离它添加的额外垃圾。 (我认为你可以使用str_replace()将多余的内容替换为空字符串。)


很遗憾,Tidy似乎不是一个有效的选择,因为它可能没有被安装或在所有的PHP主机上启用。(这个项目将会被分发)。然而,我不确定Tidy的可用性程度,所以如果我说错了,请随时纠正我。两个文本区域肯定可以解决问题,但是如果可能的话,我希望保持用户界面的简洁,所以我想先探索其他选项。 - VirtuosiMedia

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