如何使用DOM API有条件地将元素包装在一起?

时间:2019-02-24 18:54:28

标签: php xml dom xpath

假设我们输入以下内容:

void

所需的输出应为:

#include <functional>
#include <iostream>

template <typename F, typename ... As>
static auto BindAction (F const & func, As && ... args)
 { return std::bind(func, std::forward<As>(args)...); }

void Test(float f, std::string s, int i)
 { std::cout << "f[" << f << "], s[" << s << "], i[" << i << "]" << std::endl; } 

int main ()
 { 
   auto ba = BindAction(Test, 5.f, "a string", 2);

   std::cout << "post BindAction(), pre ba()\n";

   ba();
 }

此外,假设这些元素是body元素的直接子元素,并且在它们之前或之后可能还有其他不相关的元素或文本节点。

请注意,连续元素如何在单个包装中分组而不是单独包装。

您将如何处理主体的DOMNodeList并将包装器插入正确的位置?

遵循仅包装body元素的直接子元素的对话(注释),

对于此输入:

<div wrap>1</div>
<div>2</div>
<div wrap>3</div>
<div wrap>4</div>
<div wrap>5</div>

所需的输出应为:

<div class="wrapper">
  <div wrap>1</div>
</div>
<div>2</div>
<div class="wrapper">
  <div wrap>3</div>
  <div wrap>4</div>
  <div wrap>5</div>
</div>

请注意,完全忽略主体元素的不是直接后代的元素。

1 个答案:

答案 0 :(得分:1)

写作很有趣,很高兴看到其他解决方案,但是无论如何,这是我的尝试。

我在代码中添加了注释,而不是在此处描述方法,因为我认为注释使注释更容易理解...

// Test HTML
$startHTML = '<div wrap>1</div>
<div>2</div>
<div wrap>3</div>
<div wrap>4</div>
<div wrap>5</div>';

$doc = new DOMDocument();
$doc->loadHTML($startHTML);

$xp = new DOMXPath($doc);
// Find any div tag with a wrap attribute which doesn't have an immediately preceeding
// tag with a wrap attribute, (or the first node which means it won't have a preceeding
// element anyway)
$wrapList = $xp->query("//div[@wrap='' and preceding-sibling::*[1][not(@wrap)]
                           or position() = 1]");

// Iterate over each of the first in the list of wrapped nodes
foreach ( $wrapList as $wrap )  {
    // Create new wrapper 
    $wrapper = $doc->createElement("div");
    $class = $doc->createAttribute("class");
    $class->value = "wrapper";
    $wrapper->appendChild($class);

    // Copy subsequent wrap nodes (if any)
    $nextNode = $wrap->nextSibling;
    while ( $nextNode ) {
        $next = $nextNode;
        $nextNode = $nextNode->nextSibling;
        // If it's an element (and not a text node etc)
        if ( $next->nodeType == XML_ELEMENT_NODE ) {
            // If it also has a wrap attribute - copy it
            if ($next->hasAttribute("wrap") ) {
                $wrapper->appendChild($next);
            }
            // If no attribute, then finished copying
            else    {
                break;
            }
        }
    }
    // Replace first wrap node with new wrapper
    $wrap->parentNode->replaceChild($wrapper, $wrap);
    // Move the wrap node into the wrapper
    $wrapper->insertBefore($wrap, $wrapper->firstChild);
}
echo $doc->saveHTML();

使用HTML时,最终结果也都包裹在标准标记中,但是输出(格式化的)是...

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
    <body>
        <div class="wrapper">
            <div wrap>1</div>
        </div>
        <div>2</div>
        <div class="wrapper">
            <div wrap>3</div>
            <div wrap>4</div>
            <div wrap>5</div>
        </div>

    </body>
</html>

编辑:

如果仅希望将其应用于<body>标记的直接后代,则更新XPath表达式以将其包括为条件的一部分...

$wrapList = $xp->query("//body/div[@wrap='' and preceding-sibling::*[1][not(@wrap)]
                       or position() = 1]");