0

我有这个字符串:

<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 是第 1 个(子页面 A)并且有父 ID 2 和 0 个孩子,

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

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

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

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

ID 8 是第 1 个(子页面 I)并且有父 ID 7 和 0 个孩子,

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

如果这太难了,任何人都可以建议如何使用另一种方法从该字符串中获取该信息吗?

4

3 回答 3

2

那不是“字符串”,而是 HTML。您需要使用像DOMDocumentsimple_html_dom这样的 HTML 解析器。

请参阅http://htmlparsing.com/php.html中的示例

于 2012-12-29T17:54:42.977 回答
1

你可以在这里划分问题。一件事是解析 HTML,这很容易用DOMDocumentand完成DOMXpath。那是在另一个 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 上下文,例如,如果您想要不同的 ordering 或 children 处理。

理论就够了。考虑标准设置:

$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   |          |
+----+----------------+-------+--------+----------+

您可以在此处在线找到演示

于 2012-12-29T19:28:15.253 回答
0

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

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 '+----+----------------+-------+--------+----------+
';
}
于 2012-12-29T21:52:47.367 回答