php - 根据分隔符切片 HTML

标签 php html parsing dom dom-manipulation

我正在将 Word 文档即时转换为 HTML,并且需要根据分隔符解析所述 HTML。例如:

<div id="div1">
    <p>
        <font>
            <b>[[delimiter]]Start of content section 1.</b>
        </font>
    </p>
    <p>
        <span>More content in section 1</span>
    </p>
</div>
<div id="div2">
    <p>
        <b>
            <font>[[delimiter]]Start of section 2</font>
        </b>
    <p>
    <span>More content in section 2</span>
    <p><font>[[delimiter]]Start of section 3</font></p>
<div>
<div id="div3">
    <span><font>More content in section 3</font></span>
</div>
<!-- This continues on... -->

应该解析为:

第 1 部分:

<div id="div1">
    <p>
        <font>
            <b>[[delimiter]]Start of content section 1.</b>
        </font>
    </p>
    <p>
        <span>More content in section 1</span>
    </p>
</div>

第 2 部分:

<div id="div2">
    <p>
        <b>
            <font>[[delimiter]]Start of section 2</font>
        </b>
    <p>
    <span>More content in section 2</span>
    <p></p>
<div>

第 3 部分:

<div id="div2">
    <p>
        <b>

        </b>
    <p>
    <p><font>[[delimiter]]Start of section 3</font></p>
<div>
<div id="div3">
    <span><font>More content in section 3</font></span>
</div>
  1. 我不能简单地根据分隔符“分解”/切片,因为那样会破坏 HTML。每一点文本内容都有许多父元素。

  2. 我无法控制 HTML 结构,它有时会根据 Word 文档的结构发生变化。最终用户将导入他们的 Word 文档以在应用程序中进行解析,因此生成的 HTML 在解析之前不会被更改。

  3. 内容通常位于 HTML 中的不同深度。

  4. 我不能依赖元素类或 ID,因为它们在文档之间不一致。 #div1、#div2 和#div3 在我的例子中只是为了说明。

  5. 我的目标是解析内容,所以如果有空元素遗留下来也没关系,我可以简单地再次运行标记并删除空标签(p、font、b 等)。

我的尝试:

我正在使用 PHP DOM 扩展来解析 HTML 并循环遍历节点。但我想不出一个好的算法来解决这个问题。

$doc = new \DOMDocument();
$doc->loadHTML($html);
$body = $doc->getElementsByTagName('body')->item(0);

foreach ($body->childNodes as $child) {
    if ($child->hasChildNodes()) {
        // Do recursive call...
    } else {
        // Contains slide identifier?
    }
}

最佳答案

为了解决这样的问题,您首先需要确定获得解决方案所需的步骤,甚至在开始编码之前。

  1. 查找以[[delimiter]]开头的元素
  2. 检查它的父级是否有下一个兄弟
  3. 没有?重复 2
  4. 是吗?下一个兄弟包含内容。

现在,一旦您将其付诸实践,您就已经准备就绪了 90%。您只需清除不需要的标签即可。

要获得可以扩展的东西,不要构建一大堆有效的混淆代码,而是将所需的所有数据拆分到可以使用的东西中。

下面的代码与两个类一起工作,它们完全满足您的需要,并为您提供了一种很好的方式来遍历所有元素,一旦您需要它们。它确实使用 PHP Simple HTML DOM Parser而不是 DOMDocument,因为我更喜欢它。

<?php
error_reporting(E_ALL);
require_once("simple_html_dom.php");

$html = <<<XML
<body>
        <div id="div1">
                <p>
                        <font>
                                <b>[[delimiter]]Start of content section 1.</b>
                        </font>
                </p>
                <p>
                        <span>More content in section 1</span>
                </p>
        </div>
        <div id="div2">
                <p>
                        <b>
                                <font>[[delimiter]]Start of section 2</font>
                        </b>
                </p>
                <span>More content in section 2</span>
                <p>
                        <font>[[delimiter]]Start of section 3</font>
                </p>
        </div>
        <div id="div3">
                <span>
                        <font>More content in section 3</font>
                </span>
        </div>
</body>
XML;



/*
 * CALL
 */

$parser = new HtmlParser($html, '[[delimiter]]');

//dump found
//decode/encode to only show public values
print_r(json_decode(json_encode($parser)));


/*
 * ACTUAL CODE
 */


class HtmlParser
{
    private $_html;
    private $_delimiter;
    private $_dom;

    public $Elements = array();

    final public function __construct($html, $delimiter)
    {
        $this->_html = $html;
        $this->_delimiter = $delimiter;
        $this->_dom = str_get_html($this->_html);

        $this->getElements();
    }

    final private function getElements()
    {
        //this will find all elements, including parent elements
        //it will also select the actual text as an element, without surrounding tags
        $elements = $this->_dom->find("[contains(text(),'".$this->_delimiter."')]");

        //find the actual elements that start with the delimiter
        foreach($elements as $element) {
            //we want the element without tags, so we search for outertext
            if (strpos($element->outertext, $this->_delimiter)===0) {
                $this->Elements[] = new DelimiterTag($element);
            }
        }

    }

}

class DelimiterTag
{
    private $_element;

    public $Content;
    public $MoreContent;

    final public function __construct($element)
    {
        $this->_element = $element;
        $this->Content = $element->outertext;


        $this->findMore();
    }

    final private function findMore()
    {
        //we need to traverse up until we find a parent that has a next sibling
        //we need to keep track of the child, to cleanup the last parent
        $child = $this->_element;
        $parent = $child->parent();
        $next = null;
        while($parent) {
            $next = $parent->next_sibling();

            if ($next) {
                break;
            }
            $child = $parent;
            $parent = $child->parent();
        }

        if (!$next) {
            //no more content
            return;
        }

        //create empty element, to build the new data
        //go up one more element and clean the innertext
        $more = $parent->parent();
        $more->innertext = "";

        //add the parent, because this is where the actual content lies
        //but we only want to add the child to the parent, in case there are more delimiters
        $parent->innertext = $child->outertext;
        $more->innertext .= $parent->outertext;

        //add the next sibling, because this is where more content lies
        $more->innertext .= $next->outertext;

        //set the variables
        if ($more->tag=="body") {
            //Your section 3 works slightly different as it doesn't show the parent tag, where the first two do.
            //That's why i show the innertext for the root tag and the outer text for others.
            $this->MoreContent = $more->innertext;
        } else {
            $this->MoreContent = $more->outertext;
        }

    }
}




?>

清理输出:

stdClass Object
(
  [Elements] => Array
  (
    [0] => stdClass Object
    (
        [Content] => [[delimiter]]Start of content section 1.
        [MoreContent] => <div id="div1">
                            <p><font><b>[[delimiter]]Start of content section 1.</b></font></p>
                            <p><span>More content in section 1</span></p>
                          </div>
    )

    [1] => stdClass Object
    (
        [Content] => [[delimiter]]Start of section 2
        [MoreContent] => <div id="div2">
                            <p><b><font>[[delimiter]]Start of section 2</font></b></p>
                            <span>More content in section 2</span>
                         </div>
    )

    [2] => stdClass Object
    (
        [Content] => [[delimiter]]Start of section 3
        [MoreContent] => <div id="div2">
                            <p><font>[[delimiter]]Start of section 3</font></p>
                         </div>
                         <div id="div3">
                            <span><font>More content in section 3</font></span>
                          </div>
    )
  )
)

关于php - 根据分隔符切片 HTML,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45822082/

相关文章:

SwiftSoup 将 JSON 解析为 HTML 未找到 (Swift)

java - 所有 Java 标准类和方法的简单列表?

javascript - 日历上的下一个和上一个功能

php - 如何在 Laravel 表单验证错误消息中提供自定义字段名称

php - 如何让脚本在后台运行 - 不工作

php - PHP如何使用localhost用户和特定绑定(bind)地址连接到mysql

javascript - 使用 Javascript 在 &lt;header&gt; 标签上方编写代码

html - 标题图像不缩放到其他屏幕

html - 如何在 Bootstrap Jumbotron 中专门定位文本?

c# - 在 C# 中解析 SQL 字符串