我有这个字符串:
<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个孩子。
如果这太难了,任何人都可以使用其他方法从这个字符串中获取信息吗?
答案 0 :(得分:2)
这不是“字符串”,而是HTML。您需要使用DOMDocument或simple_html_dom等HTML解析器。
上的示例答案 1 :(得分:1)
你可以在这里分解问题。一件事是解析HTML,这在DOMDocument
和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值之间的相似之处在于它们是从上下文中检索的。
E.g。在按文档顺序遍历//li
节点列表时,如果每个ParentID保留一个计数器,则可以对每个子节点的顺序进行编号。
与子项目类似,就像计数器一样,需要在迭代列表时构建该值。只有在最后,每个列表项的正确值才可用。
所以这两个值都在上下文中,我以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。
答案 2 :(得分: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 '+----+----------------+-------+--------+----------+
';
}