如何在php中使用ULs将多组LIs包装在一个字符串中
我从我无法影响的不同来源获取数据(带有一些html的字符串)。字符串包含(但不限于)可视分组的如何在php中使用ULs将多组LIs包装在一个字符串中,php,html,Php,Html,我从我无法影响的不同来源获取数据(带有一些html的字符串)。字符串包含(但不限于)可视分组的LI元素,但缺少父UL元素。我需要用UL标签包装LI标签组 如果一个字符串中只有一组LI元素,则此方法可以正常工作。我可以很容易地使用DOMDocument,搜索LI标记,并用新创建的UL标记将它们包装起来。不幸的是,可以有多个组,并且组之间的分离没有定义——但总是某种文本或html标记。很容易将这些群体视为人类:) 所以从逻辑上讲,我需要找到一个开头作为组的起点,一个结尾,后面没有另一个开头作为终点,
LI
元素,但缺少父UL
元素。我需要用UL
标签包装LI
标签组
如果一个字符串中只有一组LI
元素,则此方法可以正常工作。我可以很容易地使用DOMDocument
,搜索LI
标记,并用新创建的UL
标记将它们包装起来。不幸的是,可以有多个组,并且组之间的分离没有定义——但总是某种文本或html标记。很容易将这些群体视为人类:)
所以从逻辑上讲,我需要找到一个开头
作为组的起点,一个结尾,后面没有另一个开头作为终点,忽略所有空格
示例源字符串可以是(它不总是有新行,也不总是那么漂亮):
它工作正常,直到它不工作。例如,如果某些LI
-块已经具有包装UL
:)
由于纯文本未被视为子节点,因此我所有的DOMDocument
方法都失败。这意味着我能够找到LI
s并检查它们的兄弟姐妹是否LI
s,然后将它们全部包装在UL
中(如果后一种情况适用)。但是,如果LI
-组仅由一些没有任何HTML
标记的文本分隔,则所有LI
都被视为没有任何分隔的直接同级。我不会使用正则表达式解析HTML(我们都看到这一点,所以回答:-p)
下面是一个逐行分解文本的解决方案:
<?php
function isLi($line) {
return strstr($line, '<li');
}
$text = 'Some text
<strong>Some other text</strong>
<li>Element A1</li><li>Element A2</li>
<li>Element A3</li>
Text that separates group A from group B
<li>Element B1</li>
<li>Element B2</li> <li>Element B3</li>
<li>Element B4</li>
<strong>Element that separates group B from group C</strong>
<li>Element C1</li>
<li>Element C2</li>
Text can follow.
<li>Hello, nothing follows this</li>';
$array = explode("\n", $text);
$html = '';
$previousWasLi = false;
foreach ($array as $line) {
if (empty($line)) {
continue;
}
if (isLi($line) && $previousWasLi == false) {
$html .= "<ul>\n";
$html .= $line ."\n";
$previousWasLi = true;
} elseif (isLi($line) && $previousWasLi == true) {
$html .= $line ."\n";
$previousWasLi = true;
} elseif (!isLi($line) && $previousWasLi == true) {
$html .= "</ul>\n";
$html .= $line ."\n";
$previousWasLi = false;
} elseif (!isLi($line) && $previousWasLi == false) {
$html .= $line ."\n";
}
}
// if the last line was an li, we need to close the ul
if ($previousWasLi) {
$html .= '</ul>';
}
echo $html;
在使用@delboy1978uk解决方案之前,您可以使用以下代码“几乎完全格式化”代码1步骤:
<?php
// $code_to_split is your code
$text = implode("\n<li", explode('<li', implode("</li>\n", explode('</li>', $code_to_split))));
function fnIsComplete($totest){
return (strpos(' '.$totest, '</li>')>0);
}
// use @delboy1978uk solution over $text
// add a param $iscomplete = false as 2° line
// inserting a validation rule to know if a line is <li ... > </li> complete
// add a test at } elseif (!isLi($line) && $previousWasLi == true) { block
} elseif (!isLi($line) && $previousWasLi == true) {
if($iscomplete ){
$html .= "</ul>\n";
$html .= $line ."\n";
$previousWasLi = false;
}elseif(fnIsComplete($line)) {
$html .= $line ."\n";
$html .= "</ul>\n";
$previousWasLi = false;
}else{
$html .= $line ."\n";
}
}
// and when you set $previousWasLi = true; you set also $iscomplete
$previousWasLi = true; $iscomplete = fnIsComplete($line);
我能想到的最简单的解决方案是:
通过用替换每个和用
替换每个将每个包装成
标签
- 删除所有的
,后面跟着
,忽略中间的所有空格和换行符
代码应尽可能简单:
// first step
$txt = str_replace('<li>', '<ul><li>', $source_txt);
$txt = str_replace('</li>', '</li></ul>', $txt);
// second step
$txt = preg_replace('/<\/ul>\s*<ul>/', '', $txt);
//第一步
$txt=str_replace(“”、“- ”、$source_txt);
$txt=str_replace(“
”、“
”、$txt);
//第二步
$txt=preg_replace('/\s*/',''$txt);
如果@Pilan在评论中提到,已经被
包装,您可以添加第三个步骤,删除
,然后是另一个
,然后是另一个
:
// third step
$txt = preg_replace('/<ul>\s*<ul>/', '<ul>', $txt);
$txt = preg_replace('/<\/ul>\s*<\/ul>/', '</ul>', $txt);
//第三步
$txt=preg_replace('/\s*/','',$txt);
$txt=preg_replace('/\s*/','
',$txt);
最好是将过程分割成更小的步骤
- 查找所有
li
标记
- 根据它们之间的文本对它们进行分组
- 注入
ul
标签
它为您提供了更大的灵活性,比如修复丢失的结束标记
class LiFormatter{
public $html;
private $lis;
private $groups;
public function __construct($html){
$this->html = $html;
$this->lis = [];
$this->groups = [];
$this->findNextLi(0);
if(count($this->lis)==0)
return;
$this->determineGroups();
$this->wrap();
}
private function findNextLi($offset){
$html = $this->html;
$start_index = strpos($html,'<li>',$offset);
if($start_index===false)
return;
$end_index = strpos($html,'</li>',$start_index+4);
$next_index = strpos($html,'<li>',$start_index+4);
if($next_index!==false && $next_index<$end_index){
// handle missing closing tag
$this->insertAt('</li>',$next_index);
$end_index = $next_index;
}
$this->lis[] = ['start' => $start_index, 'end'=>$end_index+5];
$this->findNextLi($end_index);
}
private function determineGroups(){
while(count($this->lis)>0){
$last_li = array_shift($this->lis);
$group = [$last_li];
while(count($this->lis)>0){
$current_li = $this->lis[0];
$str_between = substr($this->html,$last_li['end'],$current_li['start']-$last_li['end']);
if($this->isSeperating($str_between)){
break;
}else{
$group[] = $current_li;
array_shift($this->lis);
$last_li = $current_li;
}
}
$this->groups[] = $group;
}
}
private function wrap(){
$offset = 0;
foreach ($this->groups as $group) {
$first_li = reset($group);
$last_li = end($group);
$group_start = $first_li['start'];
$group_end = $last_li['end'];
$this->insertAt('<ul>',$group_start + $offset);
$offset += 4;
$this->insertAt('</ul>',$group_end + $offset);
$offset += 5;
}
}
private function insertAt($str,$index){
$this->html = substr($this->html,0,$index) . $str . substr($this->html,$index);
}
private function isSeperating($str){
return preg_match("/\w/", $str);
}
}
正则表达式是的,请强>
如果愿意,可以将其移植到PHP。仅用于JS中的演示目的
var响应=“一些文本一些其他文本元素A1 元素A2
元素A3
将A组与B组分开的文本元素B1 元素B2
元素B3元素B4元素将B组与C组分开的元素元素C1 元素C2文本可以跟随。”;
var r=响应。替换(/(?\s*)/g,- );/
变量r=r.替换(/(?!\s*- )/g,
);//
$(“#结果”).html(r);
检查当前LI元素的下一个/上一个同级元素是否仍然是LI或其他元素更有意义……我认为最好的解决方案是通过PHP XML DOM解析器()解析HTML字符串然后应用一些算法,比如平衡圆括号问题。我给你的最好建议是组织你的数据源。通过这样做,你将能够循环通过,li
的每个集合将更容易控制/操作。如果可以的话,这将是非常好的。不幸的是,数据源是我无法影响的第三方。谢谢你的支持努力:)我应该提到字符串的格式不是一直都很好。它来自不同的来源。有断行和无断行;新行在内或没有;等等。但是我会做一些实验,事先整理它-也许这是一个足够的解决方案。啊,我想问你太棒了!无论如何,祝你好运,让我知道你进展如何!@antesoles如果是这样的话,那么你需要更新你的问题以反映需求。有时候,生活可能很简单。很好,但是如果已经有了?
你会在文本中得到,你必须检查/(?\s*)- /g
替换为和/(?!\s*
)/g
替换为
-因此这里所有解决方案的组合-良好的团队合作!:D@Pilan:添加了解决这些边缘情况的第三个步骤。我刚刚将所有
和
替换为
占位符标记(以保持组之间的分离),继续第1步和第2步,然后删除了
标记。我想,结果是相同的,只是有一些微小的速度差异。谢谢。这或多或少是我已经有的解决方案:)
<?php
// $code_to_split is your code
$text = implode("\n<li", explode('<li', implode("</li>\n", explode('</li>', $code_to_split))));
function fnIsComplete($totest){
return (strpos(' '.$totest, '</li>')>0);
}
// use @delboy1978uk solution over $text
// add a param $iscomplete = false as 2° line
// inserting a validation rule to know if a line is <li ... > </li> complete
// add a test at } elseif (!isLi($line) && $previousWasLi == true) { block
} elseif (!isLi($line) && $previousWasLi == true) {
if($iscomplete ){
$html .= "</ul>\n";
$html .= $line ."\n";
$previousWasLi = false;
}elseif(fnIsComplete($line)) {
$html .= $line ."\n";
$html .= "</ul>\n";
$previousWasLi = false;
}else{
$html .= $line ."\n";
}
}
// and when you set $previousWasLi = true; you set also $iscomplete
$previousWasLi = true; $iscomplete = fnIsComplete($line);
// first step
$txt = str_replace('<li>', '<ul><li>', $source_txt);
$txt = str_replace('</li>', '</li></ul>', $txt);
// second step
$txt = preg_replace('/<\/ul>\s*<ul>/', '', $txt);
// third step
$txt = preg_replace('/<ul>\s*<ul>/', '<ul>', $txt);
$txt = preg_replace('/<\/ul>\s*<\/ul>/', '</ul>', $txt);
class LiFormatter{
public $html;
private $lis;
private $groups;
public function __construct($html){
$this->html = $html;
$this->lis = [];
$this->groups = [];
$this->findNextLi(0);
if(count($this->lis)==0)
return;
$this->determineGroups();
$this->wrap();
}
private function findNextLi($offset){
$html = $this->html;
$start_index = strpos($html,'<li>',$offset);
if($start_index===false)
return;
$end_index = strpos($html,'</li>',$start_index+4);
$next_index = strpos($html,'<li>',$start_index+4);
if($next_index!==false && $next_index<$end_index){
// handle missing closing tag
$this->insertAt('</li>',$next_index);
$end_index = $next_index;
}
$this->lis[] = ['start' => $start_index, 'end'=>$end_index+5];
$this->findNextLi($end_index);
}
private function determineGroups(){
while(count($this->lis)>0){
$last_li = array_shift($this->lis);
$group = [$last_li];
while(count($this->lis)>0){
$current_li = $this->lis[0];
$str_between = substr($this->html,$last_li['end'],$current_li['start']-$last_li['end']);
if($this->isSeperating($str_between)){
break;
}else{
$group[] = $current_li;
array_shift($this->lis);
$last_li = $current_li;
}
}
$this->groups[] = $group;
}
}
private function wrap(){
$offset = 0;
foreach ($this->groups as $group) {
$first_li = reset($group);
$last_li = end($group);
$group_start = $first_li['start'];
$group_end = $last_li['end'];
$this->insertAt('<ul>',$group_start + $offset);
$offset += 4;
$this->insertAt('</ul>',$group_end + $offset);
$offset += 5;
}
}
private function insertAt($str,$index){
$this->html = substr($this->html,0,$index) . $str . substr($this->html,$index);
}
private function isSeperating($str){
return preg_match("/\w/", $str);
}
}
$output = (new LiFormatter($input))->html;