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:

  1. if (toExpand.StartsWith(startForeach))
  2. {
  3.   endPos = s.IndexOf(endForeach, endPos);   // <– This line finds the next {/foreach} – not the matching one
  4.   string repeatBlock = s.Substring(i + toExpand.Length + 2, endPos – i – toExpand.Length2);
  5.   expanded.Append(ExpandBlock(roots, toExpand, repeatBlock));
  6.   i += toExpand.Length + repeatBlock.Length + endForeach.Length + 1;
  7.   break;
  8. }

Instead, that has to change to

  1. if (toExpand.StartsWith(startForeach))
  2. {
  3.   endPos = GetMatchingEndPos(s, endPos);
  4.   if (endPos == -1)
  5.   throw new InvalidOperationException(string.Format("Unable to find matching /foreach after start on pos {0}", i));
  6.   string repeatBlock = s.Substring(i + toExpand.Length + 2, endPos – i – toExpand.Length2);
  7.   expanded.Append(ExpandBlock(roots, toExpand, repeatBlock));
  8.   i += toExpand.Length + repeatBlock.Length + endForeach.Length + 1;
  9.   break;
  10. }

Again, interesting lines of code somewhere else (the method was already way too long for my taste anyway!}

  1. private int GetMatchingEndPos(string s, int startPos)
  2. {
  3.   int result;
  4.   int openCount = 1;
  5.   do
  6.   {
  7.     result = s.IndexOf(endForeach, startPos);
  8.     int nextOpen = s.IndexOf(startForeach, startPos);
  9.  
  10.     if (nextOpen == -1 || nextOpen > result)
  11.     {
  12.       openCount–;
  13.       startPos = result + 1;
  14.     }
  15.     else
  16.     {
  17.       openCount++;
  18.       startPos = nextOpen + 1;
  19.     }
  20.   } while (openCount > 0);
  21.   return result;
  22. }

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

  • Share/Bookmark

Comments (2)

jonas hogstromDecember 18th, 2008 at 12:16 am

nice work!

Joe HendricksDecember 19th, 2008 at 6:55 pm

Thanks Jesper !

Leave a comment

Your comment