【问题标题】:How can I extract structured text from an HTML list in PHP?如何从 PHP 中的 HTML 列表中提取结构化文本?
【发布时间】:2012-12-29 17:47:13
【问题描述】:

我有这个字符串:

<ul>
  <li id="1">Page 1</li>
  <li id="2">Page 2
    <ul>
      <li id="3">Sub Page A</li>
      <li id="4">Sub Page B</li>
      <li id="5">Sub Page C
        <ul>
          <li id="6">Sub Sub Page I</li>
        </ul>
      </li>
    </ul>
  </li>
  <li id="7">Page 3
    <ul>
      <li id="8">Sub Page D</li>
    </ul>
  </li>
  <li id="9">Page 4</li>
</ul>

我想用 PHP 分解每一个信息,并把它变成这样:

----------------------------------
| ID | ORDER | PARENT | CHILDREN |
----------------------------------
|  1 |   1   |   0   |     0     |
|  2 |   2   |   0   |   3,4,5   |
|  3 |   1   |   2   |     0     |
|  4 |   2   |   2   |     0     |
|  5 |   3   |   2   |     6     |
|  6 |   1   |   5   |     0     |
|  7 |   3   |   0   |     8     |
|  8 |   1   |   7   |     0     |
|  9 |   4   |   0   |     0     |
----------------------------------

有关更多信息,这就是此列表对我的意义:

ID 1 是第 1(第 1 页)并且有 0 个父母和 0 个孩子,

ID 2 是第 2 个(第 2 页)并且有 0 个父母和孩子 ID 3、4、5,

ID 3 是第一个(子页面 A)并且有父 ID 2 和 0 个子,

ID 4 是第二个(子页面 B)并且有父 ID 2 和 0 个子,

ID 5 是第 3 个(子页面 C),父 ID 为 2,子 ID 为 6,

ID 6 是第一个(子页面 I)并且有父 ID 5 和 0 个子,

ID 7 是第 3 个(第 3 页)并且有 0 个父母和孩子 ID 8,

ID 8 是第 1 个(子页面 I),父 ID 为 7,子 ID 为 0,

ID 9 是第 4 个(第 4 页),有 0 个父母和 0 个孩子。

如果这太难了,谁能建议如何用另一种方法从这个字符串中获取该信息?

【问题讨论】:

  • 您尝试过什么来实现这一目标?
  • 为什么 php 你可以通过 jquery 本身来检索它。

标签: php html-parsing


【解决方案1】:

这不是“字符串”,而是 HTML。您需要使用 HTML 解析器,例如 DOMDocumentsimple_html_dom

http://htmlparsing.com/php.html查看示例

【讨论】:

  • 不,我的意思是 PHP 字符串 $my_str = '
    ';
  • 它是一个字符串,但它是一个包含标准化标记 (HTML) 数据的字符串,因此使用现有的解析器而不是自己编写是有意义的。
  • 是的,HTML 恰好位于 PHP 字符串中,但该字符串包含 HTML,您需要使用 HTML 解析器解析该 HTML。如果有人告诉您使用正则表达式,请忽略它们。使用 HTML 解析器。
【解决方案2】:

你可以在这里划分问题。一件事是解析 HTML,这很容易用 DOMDocumentDOMXpath 在这里完成。那是在另一个 xpath 表达式/查询的结果的上下文中运行一些映射。听起来可能有点复杂,但事实并非如此。在更简化的变体中,您可以在之前对 Get parent element through xpath and all child elements 的回答中找到这一点。

在您的情况下,这有点复杂,是一些伪代码。我添加了标签,因为它使事物更加明显以用于演示目的:

foreach //li ::
    ID       := string(./@id)
    ParentID := string(./ancestor::li[1]/@id)
    Label    := normalize-space(./text()[1])

如图所示,这仅返回裸数据。你也有骑士团和孩子们。通常不需要儿童列表(无论如何我都保留在这里)。 Order 值和 Children 值之间的相似之处在于它们是从上下文中检索的。

例如在按文档顺序遍历//li 节点列表时,如果为每个 ParentID 保留一个计数器,则可以对每个子节点的顺序进行编号。

与 Children 类似,就像计数器一样,需要在迭代列表时构建该值。只有在最后,每个列表项的正确值才可用。

所以这两个值在一个上下文中,我以 ParentID 键控的数组的形式创建该上下文:$parents。对于每个 ID,它将包含两个条目:0 包含 Order 的计数器,1 包含一个数组以保存 Children 的 ID(如果有)。

注意:从技术上讲,这并不完全正确。 Order 和 Children 也应该可以在纯 xpath 中表达,我只是在这个示例中没有这样做来展示如何添加您自己的非 xpath 上下文,例如如果您想要不同的排序或儿童处理。

理论说得够多了。考虑标准设置:

$doc = new DOMDocument();
$doc->loadHTML($html);
$xp = new DOMXPath($doc);

上述映射包括。它的上下文可以写成匿名函数:

$parents = [];

$map = function (DOMElement $li) use ($xp, &$parents) {

    $id       = (int)$xp->evaluate('string(./@id)', $li);
    $parentId = (int)$xp->evaluate('string(./ancestor::li[1]/@id)', $li);
    $label    = $xp->evaluate('normalize-space(./text()[1])', $li);

    isset($parents[$parentId][0]) ? $parents[$parentId][0]++ : ($parents[$parentId][0] = 1);
    $order                   = $parents[$parentId][0];
    $parents[$parentId][1][] = $id;
    isset($parents[$id][1]) || $parents[$id][1] = [];

    return array($id, $label, $order, $parentId, &$parents[$id][1]);
};

如您所见,它首先包含对伪代码中的值的检索,第二部分包含对上下文值的处理。如果 ID / ParentID 不存在,它只是初始化上下文。

需要应用此映射:

$result = [];
foreach ($xp->query('//li') as $li) {
    list($id) = $array = $map($li);
    $result[$id] = $array;
}

这将使$result 包含项目列表,$parents 包含上下文数据。由于使用了引用,现在需要对 Children 值进行内爆,然后可以删除引用:

foreach ($parents as &$parent) {
    $parent[1] = implode(',', $parent[1]);
}
unset($parent, $parents);

这使得$result成为可以输出的最终结果:

echo '+----+----------------+-------+--------+----------+
| ID |     LABEL      | ORDER | PARENT | CHILDREN |
+----+----------------+-------+--------+----------+
';
foreach ($result as $line) {
    vprintf("| %' 2d | %' -14s |  %' 2d   |   %' 2d   | %-8s |\n", $line);
}
echo '+----+----------------+-------+--------+----------+
';

然后看起来像:

+----+----------------+-------+--------+----------+
| ID |     LABEL      | ORDER | PARENT | CHILDREN |
+----+----------------+-------+--------+----------+
|  1 | Page 1         |   1   |    0   |          |
|  2 | Page 2         |   2   |    0   | 3,4,5    |
|  3 | Sub Page A     |   1   |    2   |          |
|  4 | Sub Page B     |   2   |    2   |          |
|  5 | Sub Page C     |   3   |    2   | 6        |
|  6 | Sub Sub Page I |   1   |    5   |          |
|  7 | Page 3         |   3   |    0   | 8        |
|  8 | Sub Page D     |   1   |    7   |          |
|  9 | Page 4         |   4   |    0   |          |
+----+----------------+-------+--------+----------+

您可以找到Demo online here

【讨论】:

    【解决方案3】:

    我留下第二个答案,因为这一次演示了如何使用单个映射(在伪代码中):

    foreach //li ::
        ID       := string(./@id)
        ParentID := string(./ancestor::li[1]/@id)
        Label    := normalize-space(./text()[1])
        Order    := count(./preceding-sibling::li)+1
        Children := implode(",", ./ul/li/@id)
    

    因为这可以在每个li 节点上完成,无论以何种顺序,这可能是Iterator 的完美匹配,这里是当前函数:

    public function current() {
    
        return [
            'ID'       => $this->evaluate('number(./@id)'),
            'label'    => $this->evaluate('normalize-space(./text()[1])'),
            'order'    => $this->evaluate('count(./preceding-sibling::li)+1'),
            'parentID' => $this->evaluate('number(concat("0", ./ancestor::li[1]/@id))'),
            'children' => $this->implodeNodes(',', './ul/li/@id'),
        ];
    }
    

    完整示例 (Demo) 输出和代码:

    +----+----------------+-------+--------+----------+
    | ID |     LABEL      | ORDER | PARENT | CHILDREN |
    +----+----------------+-------+--------+----------+
    |  1 | Page 1         |   1   |    0   |          |
    |  2 | Page 2         |   2   |    0   | 3,4,5    |
    |  3 | Sub Page A     |   1   |    2   |          |
    |  4 | Sub Page B     |   2   |    2   |          |
    |  5 | Sub Page C     |   3   |    2   | 6        |
    |  6 | Sub Sub Page I |   1   |    5   |          |
    |  7 | Page 3         |   3   |    0   | 8        |
    |  8 | Sub Page D     |   1   |    7   |          |
    |  9 | Page 4         |   4   |    0   |          |
    +----+----------------+-------+--------+----------+
    
    
    class HtmlListIterator extends IteratorIterator
    {
        private $xpath;
    
        public function __construct($html) {
    
            $doc = new DOMDocument();
            $doc->loadHTML($html);
            $this->xpath = new DOMXPath($doc);
            parent::__construct($this->xpath->query('//li'));
        }
    
        private function evaluate($expression) {
    
            return $this->xpath->evaluate($expression, parent::current());
        }
    
        private function implodeNodes($glue, $expression) {
    
            return implode(
                $glue, array_map(function ($a) {
    
                    return $a->nodeValue;
                }, iterator_to_array($this->evaluate($expression, parent::current())))
            );
        }
    
        public function current() {
    
            return [
                'ID'       => $this->evaluate('number(./@id)'),
                'label'    => $this->evaluate('normalize-space(./text()[1])'),
                'order'    => $this->evaluate('count(./preceding-sibling::li)+1'),
                'parentID' => $this->evaluate('number(concat("0", ./ancestor::li[1]/@id))'),
                'children' => $this->implodeNodes(',', './ul/li/@id'),
            ];
        }
    }
    
    print_result(new HtmlListIterator($html));
    
    function print_result($result) {
    
        echo '+----+----------------+-------+--------+----------+
    | ID |     LABEL      | ORDER | PARENT | CHILDREN |
    +----+----------------+-------+--------+----------+
    ';
        foreach ($result as $line) {
            vprintf("| %' 2d | %' -14s |  %' 2d   |   %' 2d   | %-8s |\n", $line);
        }
        echo '+----+----------------+-------+--------+----------+
    ';
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-11-29
      • 2020-06-19
      • 1970-01-01
      • 2016-09-07
      • 2020-09-07
      • 1970-01-01
      • 1970-01-01
      • 2013-11-15
      相关资源
      最近更新 更多