Nested foreach in ECO report generator
As Jonas pointed out in a comment to my last post, it was not possible to nest foreach-loops in the report.
One minor change was required to support that, namely the locating of the position of the – matching – {/foreach}.
Original code looked like:
-
if (toExpand.StartsWith(startForeach))
-
{
-
endPos = s.IndexOf(endForeach, endPos); // <– This line finds the next {/foreach} – not the matching one
-
string repeatBlock = s.Substring(i + toExpand.Length + 2, endPos – i – toExpand.Length – 2);
-
expanded.Append(ExpandBlock(roots, toExpand, repeatBlock));
-
i += toExpand.Length + repeatBlock.Length + endForeach.Length + 1;
-
break;
-
}
Instead, that has to change to
-
if (toExpand.StartsWith(startForeach))
-
{
-
endPos = GetMatchingEndPos(s, endPos);
-
if (endPos == -1)
-
throw new InvalidOperationException(string.Format("Unable to find matching /foreach after start on pos {0}", i));
-
string repeatBlock = s.Substring(i + toExpand.Length + 2, endPos – i – toExpand.Length – 2);
-
expanded.Append(ExpandBlock(roots, toExpand, repeatBlock));
-
i += toExpand.Length + repeatBlock.Length + endForeach.Length + 1;
-
break;
-
}
Again, interesting lines of code somewhere else (the method was already way too long for my taste anyway!}
-
private int GetMatchingEndPos(string s, int startPos)
-
{
-
int result;
-
int openCount = 1;
-
do
-
{
-
result = s.IndexOf(endForeach, startPos);
-
int nextOpen = s.IndexOf(startForeach, startPos);
-
-
if (nextOpen == -1 || nextOpen > result)
-
{
-
openCount–;
-
startPos = result + 1;
-
}
-
else
-
{
-
openCount++;
-
startPos = nextOpen + 1;
-
}
-
} while (openCount > 0);
-
return result;
-
}
What the code does is look for the next open and close token. If the first token is “open”, increase the openCount. If the next token is “close”, decrease openCount. Next time, start looking after the first token found. If the openCount is zero, we’ve found ourselves a match.
This can be tested with template strings like
{foreach:Person.allInstances}
outer {Person:self.fullName}
{foreach:Person.allInstances}
inner {Person:self.fullName}
{/foreach}
{foreach:Person.allInstances}
inner2 {Person:self.fullName}
{/foreach}
{/foreach}
i.e. a sequence of “start start stop start stop stop”. The sequences I alread had works fine still, as well as “start start stop stop”. Should be pretty safe.
–Jesper Hogstrom
nice work!
Thanks Jesper !