Empty2k12 Empty2k12 - 6 months ago 17
PHP Question

Loop inside loop only running once

For educational purposes I'm trying to build a PUG Parser.
I have two loops, one loops the lines of the PUG-Code, the other loops unclosed tags I saved last iteration.

In my code it seems like the inner loop is only run once, because when I put

echo
statements for debugging I'm not getting enough output.

foreach ($lines as $line) {
#Increment the line number
$lineNum++;

#Save the current indentation for later use
$currentIndentation = strspn($line, " ");

#Go though list of unclosed tags and recursivley close all that need to be closed
foreach ($unclosed_tags as $lineNum => $tag) {
#Assign a variable to the tag indentation
$tagIndentation = $tag[1];

echo $tag[0] . "$tagIndentation:$currentIndentation=" . !($tagIndentation < $currentIndentation) . "<br>";

#Check if the current indentation is not smaller than the tag
if (!($tagIndentation < $currentIndentation)) {
#Close the tag
$output .= "</" . $tag[0] . ">";
#Remove the tag from unclosed list cince we just closed it
$unclosed_tags = array_diff($unclosed_tags, array($unclosed_tags[$key]));
}
}

#Get the current element (== the first string in a string)
$currentElement = preg_replace('/(\s*)([^\s]*)(.*)/', '$2', $line);

#Output as open tag
$output .= "<" . $currentElement . ">" . trim(str_replace_first($currentElement, "", $line));
#Add to list of unclosed tags
$unclosed_tags[$lineNum] = array($currentElement, $currentIndentation);
}


The sample .pug file I'm trying to parse looks like this:

html
head
title Test
body
h1 This page was defined using JADE (PUG)!
h2 This a Header 2
div
p <strong>This text will appear strong</strong>
hr
br
ul
li This
li is a
li test
ol
li <b>Ordered Test</b>
li for the win


The exported html looks like this

<html>
<head>
<title>Test</title>

<body>
<h1>This page was defined using JADE (PUG)!</h1>
<h2>This a Header 2</h2>
<div>
<p><strong>This text will appear strong</strong></p>
<hr/>
</p>
<br/>
</p>
<ul>
<li>This</li>
<li>is a</li>
<li>test</li>
<ol>
<li><b>Ordered Test</b></li>
<li>for the win</li>


Appart from closing the unclosed tags this is my complete algorithm, but it is not working since sometimes tags are not closed because the inner loop can't check if more tags need to be closed this cycle!

I am not looking for a existing library, but rather tips on why my code is not working and how to improve upon the code quality and execution time!

Answer

So I found something that works. Here is the code as an example and I'll explain the changes below:

foreach ($lines as $line) {
    #Increment the line number
    $lineNum++;

    #Save the current indentation for later use
    $currentIndentation = strspn($line, " ");

    #Go though list of unclosed tags and recursivley close all that need to be closed
    for($i=count($unclosed_tags)-1; $i>= 0; $i--){
        $tag = $unclosed_tags[$i];
        #Assign a variable to the tag indentation
        $tagIndentation = $tag[1];

        echo $tag[0] . "$tagIndentation:$currentIndentation=" . !($tagIndentation < $currentIndentation) . "<br>";

        #Check if the current indentation is not smaller than the tag
        if ($tagIndentation >= $currentIndentation) {
            #Close the tag
            $output .= "</" . $tag[0] . ">";
            #Remove the tag from unclosed list cince we just closed it
            unset($unclosed_tags[$i]);
        } else {
            break;
        }
    }

    #Get the current element (== the first string in a string)
    $currentElement = preg_replace('/(\s*)([^\s]*)(.*)/', '$2', $line);

    #Output as open tag
    $output .= "<" . $currentElement . ">" . trim(str_replace_first($currentElement, "", $line));
    #Add to list of unclosed tags
    $unclosed_tags[] = array($currentElement, $currentIndentation);
}

OK, so changes are:

  1. I changed the way you are adding to the $unclosed_tags as just a straight append. You don't need to track what the line number is here and this allows us to use a for loop as the inner loop.
  2. I changed the inner loop as a for loop. Reason is that you want to close your tags in reverse order. As you are opening tags, you want to close them in reverse order.
  3. I changed the array_diff to a simple unset of the current key in the loop. By looping over in reverse, we can also keep in the break to stop looping once we are done with the closing tags at the current indentation level.

After that, the only thing that is left would be to loop over the remaining $unclosed_tags in reverse order and close them. I guess you could also keep the line number stuff and foreach(array_reverse($unclosed_tags) as $i=>$line){ ... with the same result as the for loop.