正如我在评论中所写的那样,您首先需要找到文本偏移量来进行截取。
首先,我设置了一个包含HTML片段的DOMDocument,然后选择代表它的body元素。
$htmlFragment = <<<HTML
<p>
<span class="Underline"><span class="Bold">Test to be cut</span></span>
</p><p>Some text </p>
HTML;
$dom = new DOMDocument();
$dom->loadHTML($htmlFragment);
$parent = $dom->getElementsByTagName('body')->item(0);
if (!$parent)
{
throw new Exception('Parent element not found.');
}
然后我使用我的TextRange
类找到需要剪切的位置,使用TextRange
实际进行剪切并定位应成为片段最后一个节点的DOMNode
:
$range = new TextRange($parent);
$width = 17;
$pattern = sprintf('~^.{0,%d}(?<=\S)(?=\s)|^.{0,%1$d}(?=\s)~su', $width);
$r = preg_match($pattern, $range, $matches);
if (FALSE === $r)
{
throw new Exception('Wordcut regex failed.');
}
if (!$r)
{
throw new Exception(sprintf('Text "%s" is not cut-able (should not happen).', $range));
}
这个正则表达式可以找到在
$range
的文本表示中切割的偏移量。该正则表达式模式
受另一个答案启发,该答案对其进行了更详细的讨论,并稍作修改以适应此答案的需要。
// chop-off the textnodes to make a cut in DOM possible
$range->split($matches[0]);
$nodes = $range->getNodes();
$cutPosition = end($nodes);
由于有可能没有内容可供剪切(例如,body
可能为空),我需要处理这种特殊情况。否则,如注释中所述,所有 后续 节点都需要被删除:
// obtain list of elements to remove with xpath
if (FALSE === $cutPosition)
{
// if there is no node, delete all parent children
$cutPosition = $parent;
$xpath = 'child::node()';
}
else
{
$xpath = 'following::node()';
}
其余部分很简单:查询xpath,删除节点并输出结果:
// execute xpath
$xp = new DOMXPath($dom);
$remove = $xp->query($xpath, $cutPosition);
if (!$remove)
{
throw new Exception('XPath query failed to obtain elements to remove');
}
// remove nodes
foreach($remove as $node)
{
$node->parentNode->removeChild($node);
}
// inner HTML (PHP >= 5.3.6)
foreach($parent->childNodes as $node)
{
echo $dom->saveHTML($node);
}
完整的代码示例包括
TextRange
类,可以在 viper codepad 上
获得。由于 codepad 存在一个 bug,因此其结果不正确(相关内容请参见
XPath 查询结果顺序)。实际输出如下所示:
<p>
<span class="Underline"><span class="Bold">Test to</span></span></p>
所以请确保您有当前的libxml版本(通常是这种情况),并且最后输出中使用了PHP函数saveHTML,该参数自PHP 5.3.6起可用。如果您没有那个PHP版本,请使用其他替代方法,如
How to get the xml content of a node as a string?或类似的问题。
当您仔细查看我的示例代码时,您可能会注意到截断长度相当大(
$width = 17;
)。这是因为文本前面有许多空格字符。可以通过使正则表达式在其前面放弃任意数量的空格和/或修剪TextRange来调整它。第二个选项需要更多功能,我编写了一些快速的内容,可在创建初始范围后使用:
...
$range = new TextRange($parent);
$trimmer = new TextRangeTrimmer($range);
$trimmer->trim();
...
这将去除您的HTML片段中左侧和右侧不必要的空格。以下是TextRangeTrimmer
代码:
class TextRangeTrimmer
{
private $range;
private $charlist;
public function __construct(TextRange $range, Array $charlist = NULL)
{
$this->range = $range;
$this->setCharlist($charlist);
}
public function setCharlist(Array $charlist = NULL)
{
if (NULL === $charlist)
$charlist = str_split(" \t\n\r\0\x0B")
;
$list = array();
foreach($charlist as $char)
{
if (!is_string($char))
{
throw new InvalidArgumentException('Not an Array of strings.');
}
if (strlen($char))
{
$list[] = $char;
}
}
$this->charlist = array_flip($list);
}
public function getCharlist()
{
return array_keys($this->charlist);
}
public function trim()
{
if (!$this->charlist) return;
$this->ltrim();
$this->rtrim();
}
private function lengthOfCharacterSequence(Array $charlist, $start, $direction = 1)
{
$start = (int) $start;
$direction = max(-1, min(1, $direction));
if (!$direction) throw new InvalidArgumentException('Direction must be 1 or -1.');
$count = 0;
for(;$char = $this->range->getCharacter($start), $char !== ''; $start += $direction, $count++)
if (!isset($charlist[$char])) break;
return $count;
}
public function ltrim()
{
$count = $this->lengthOfCharacterSequence($this->charlist, 0);
if ($count)
{
$remainder = $this->range->split($count);
foreach($this->range->getNodes() as $textNode)
{
$textNode->parentNode->removeChild($textNode);
}
$this->range->setNodes($remainder->getNodes());
}
}
public function rtrim()
{
$count = $this->lengthOfCharacterSequence($this->charlist, -1, -1);
if ($count)
{
$chop = $this->range->split(-$count);
foreach($chop->getNodes() as $textNode)
{
$textNode->parentNode->removeChild($textNode);
}
}
}
}
希望这有所帮助。
nodeValue
代表什么。此外,XML有子元素,我认为你实际上想要在某个位置删除所有子元素。因此,请尝试开发一下HTML中的自动换行功能。另外,你可能需要添加一些简单的输入HTML示例,以便更清楚地了解你遇到的问题。 - hakreHTML
、HEAD
和BODY
标签是否像HTML 2一样自动生成?只是为了更好地理解你的示例代码而问一下。 - hakreHTML
,HEAD
或者BODY
标签,只有span
,a
,p
,ul
,ol
,li
和img
可用。我只是使用这些标签格式化了一些文本。由于我的代码限制了文本的最大长度,所以我认为需要分割DOM。事实上,我对DOM库还很陌生,不知道如何正确地切割DOM。我想要包装文本,但同时也希望在最后一个元素中折叠文本之后将所有其他子元素删除。 - PeekyouDOMText
节点,然后删除所有后续节点及其子节点。在此过程中,xpath axis 可能会有所帮助(使用following-sibling
和/或following
)。 - hakre