定位PHP数组中的每两项

I've been scratching my head at this for a little bit and cannot come up with a solution.

I have a text block filled with <li> elements. I have used:

$array = explode("<li>", $text_block);

to separate these li's into different array elements. I need to create a foreach loop which will print all of these elements, but also apply a div to each two elements, for example:

<div>
<li>First array item</li>
<li>Second array item</li>
</div>

<div>
<li>Third array item</li>
<li>Fourth array item</li>
</div>

And so forth unto the array is empty.

Has anybody encountered a similar problem or have any solutions towards this?

Any help would be greatly appreciated.

Jamie

The easy solution would be to use array_chunk:

$arr = range(1, 4);
foreach(array_chunk($arr, 2) as $pair) {
    echo '<div>';
    foreach ($pair as $item) {
        echo "<span>$item</span>";
    }
    echo '</div>';
}

This is straightforward to understand and not prone to bugs, but it's somewhat wasteful. The classical solution, which is not wasteful, involves the modulo operator:

$arr = range(1, 4);
$index = 0;
$inGroup = false;

foreach($arr as $item) {
    if (!$inGroup) {
        echo '<div>';
        $inGroup = true;
    }
    echo "<span>$item</span>";
    if (++$index % 2 == 0) {
        echo '</div>';
        $inGroup = false;
    }
}

if ($inGroup) echo '</div>';

However this solution has other drawbacks:

  • It's much more involved -- obvious from the code.
  • It is extremely easy to write a bug in one of the corner cases -- a nice proof of this is that both answers that do this have such problems. Possible bugs include:
    • an empty container being printed for an empty array (instead of no output which is correct)
    • the last container not being closed properly if the array contains an odd number of items
    • an empty container being printed at the end of the output if the array contains an even number of items
    • the method not working correctly if the array keys are not consecutive integers

Something like this

$i = 0;   
echo '<div>'; 
foreach($array as $item)
{
 $i++;
 echo '<li>';
 echo $item;
 echo '</li>';

if($i%2==0)
{
  echo '</div><div>';

}

}
echo '</div>';

I would do it with a simple for loop like this:

for($i=0;$i<count($array);$i++)
{

    if($i%2==0)
    {
        echo "<div>";
        echo $array[$i].$array[$i+1];
        echo "</div>";
    }

}

Your $array will look like this after using explode:

$array = array("First array item</li>", "Second array item</li>", ...);

So you might use a for loop that looks like this:

$rtn = "";

for ($i = 0; $i < count($array); $i = $i + 2) {
    $rtn .= "<div><li>" . $array[$i] . (($i+1 < count($array)) ? "<li>" . $array[$i+1] : "") . "</div>"
}

echo $rtn;

This will use the existing "half" tags and subsequently print the contents of $rtn to your page. Also, when having an odd number of <li></li> tags, the script won't crash.

Edit says: Be aware that using a structure like <div><li></li></div> will most likely to the W3C not validating your web page. If you'd like a correct HTML output, using other items is recommended. For example, you can use <div> or <span> tags:

$rtn .= "<div><span>" . substr($array[$i], 0, -5) . "</span>" . (($i+1 < count($array)) ? "<span>" . substr($array[$i+1], 0, -5) . "</span>" : "") . "</div>"

The substr($array[$i], 0, -5) is responsible for cutting the trailing </li> off.

You can try this way to match all li even if has attributes and split 2 by 2:

Content:

$content = "
    <li>List 1</li>
    <li class='ok'>List 2</li>
    <li>List 3</li>
    <li>List 4</li>
";

Generating splitted Content:

$pattern = "/<li ?.*>(.*)<\/li>/";
preg_match_all($pattern, $content, $matches);
$i = 1;
foreach ($matches[0] as $match) {
    if($i % 2 != 0) echo "<div>";
    echo $match;
    if($i % 2 == 0) echo "</div>";
    $i++;
}
if($i % 2 == 0) echo "</div>";

This should explode your li elements (assuming they have no child elements) and echo the output as you described:

// your li tags string here
$s = '<li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li>';
preg_match_all('#<li ?[^>]*>([^<]*)</li>#', $s, $matches);

foreach ($matches[1] as $i => $li) {
    if (!($i %2)) {
        if ($i > 0)
            echo '</div>';
        echo '<div>';
    }
    echo '<li>'.$li.'</li>';
    echo "
";
}
if (!($i %2)) echo '</div>';