How to break a for-each loop when value is set to prevent a double evaluation?
UPDATE: Improved the explanation so hopefully the problem is clear now :)
Hi all!
I've got a XML in which transitions of a FSM are declared (and events and states offcourse).
Here's a simplified version of the XML which also causes the problem which is described below:
<?xml version="1.0" encoding="UTF-8"?>
<FSM name="MediaPlayer">
<state name="FastForward"/>
<state name="PlayingMediaFile"/>
<transition name="FastForward to FastForward" source="FastForward" target="FastForward">
<trigger name="FastForwardButtonPressed" action="playingSpeed * 2" guard="currentMediaFile.CurrentPosition U+003C currentMediaFile.Lengtha"/>
</transition>
<transition name="FastForward to PlayingMediaFile" source="FastForward" target="PlayingMediaFile">
<trigger name="PlayButtonPressed" action="playingSpeed = 1" guard=""/>
</transition>
<event name="FastForwardButtonPressed"/>
<event name="PlayButtonPressed"/>
</FSM>
My goal is to generate a FSM in C# with XSLT. The implementation of the FSM must be according to state pattern. Because of this I need to create a class for every state. In every class, methods will represent events. Since the classes derive from a abstract class which holds all the methods (events), every class/state needs to implement all the methods(events). Offcourse not all methods are actually implemented because a state not uses all the events. Therefore I choose to implement the non used events with an exception.
For generating the C# code I need to traverse the XML and generate a class for every state. When a class is created, the next step is to generate a method for every event. For now, this is the difficult part. For every method(event) I need to check whether the event exists for the current state I'm evaluating. Therefore I traverse the XML and look for a transition which attribute "source" equals the current evaluated state/class. If found one, I check whether the trigger child is the same as the current event I'm evaluating. If both tests are true the content of "transtion/trigger/@action" and "transtion/trigger/@guard" need to be used as the implementation of the current method/event. If there isn't found a valid transition, it means the event doesn't exist for the state and therefore must be implemented as an exception.
For now I haven't written code for reading the action and guard attributes so there will be no code generated for this. For now I use the text "implement guard/action" when a transition is valid for the current state and event and use the text "throw new N开发者_如何学运维otImplementedException();" when a transition is not found. Or better said, this should be done....
I've written code which almost gets me there:
<xsl:for-each select="//state">
<xsl:variable name="currentStateName" select="@name"/>
public class <xsl:value-of select="@name"/> : <xsl:value-of select="$FsmName"/>States
{
public <xsl:value-of select="@name"/>()
{
Console.WriteLine("In <xsl:value-of select="@name"/>State");
}
<xsl:for-each select="//event">
public override void <xsl:value-of select="@name"/>(MediaPlayer player)
{
<xsl:variable name="currentEventName" select="@name"/>
<xsl:for-each select="//transition">
<xsl:if test="@source = $currentStateName">
<xsl:choose>
<xsl:when test="trigger/@name = $currentEventName">
implement guard/action
</xsl:when>
<xsl:otherwise>
throw new NotImplementedException();
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:for-each>
}
</xsl:for-each>
}
</xsl:for-each>
Now the output of this code is:
public class FastForward : MediaPlayerStates
{
public FastForward()
{
Console.WriteLine("In FastForwardState");
}
public override void FastForwardButtonPressed(MediaPlayer player)
{
implement guard/action
throw new NotImplementedException();
}
public override void PlayButtonPressed(MediaPlayer player)
{
throw new NotImplementedException();
implement guard/action
}
}
public class PlayingMediaFile : MediaPlayerStates
{
public PlayingMediaFile()
{
Console.WriteLine("In PlayingMediaFileState");
}
public override void FastForwardButtonPressed(MediaPlayer player)
{
}
public override void PlayButtonPressed(MediaPlayer player)
{
}
}
As you might understand, this is wrong. In the "FastForward" class are generated wrong. It only needs the "implement guard/action" and not also the "throw new NotImplementedException();". In the "PlayingMediaFile" class nothing gets generated while it needs be filled with "throw new NotImplementedException();" because that class has no events.
public class FastForward : MediaPlayerStates
{
public FastForward()
{
Console.WriteLine("In FastForwardState");
}
public override void FastForwardButtonPressed(MediaPlayer player)
{
implement guard/action
}
public override void PlayButtonPressed(MediaPlayer player)
{
implement guard/action
}
}
public class PlayingMediaFile : MediaPlayerStates
{
public PlayingMediaFile()
{
Console.WriteLine("In PlayingMediaFileState");
}
public override void FastForwardButtonPressed(MediaPlayer player)
{
throw new NotImplementedException();
}
public override void PlayButtonPressed(MediaPlayer player)
{
throw new NotImplementedException();
}
}
Now I understand why it is going wrong which I will try to explain: when looping all the transitions, it encounters two transitions. The first one will evaluate true to the test so it generates the "implement guard/action" text. Now it traverses to the second transition node which evaluates false. Because of the false evaluation, it generates the NotImplementedException(). This happens all for the same event and thus is wrong.
So, as far as I can see, the transition for-each loop needs to break when the test is true. If there wasn't a found a matching transition, the "NotImplementedException" text needs to be generated.
I've tried all kind of things but I can't get it done. Can somebody please help me?
Thanks
There are two <transition>
elements. Therefore, this loop:
<xsl:for-each select="//transition">
will have two iterations. One of the two iterations generates your implement guard/action
, the other generates the exception code.
Maybe you mean something like this? (Using pseudo-XSLT)
<xsl:set-variable name="anyMatched" value="0" />
<xsl:for-each select="//transition">
<xsl:if test="@source = $currentStateName">
<xsl:if test="trigger/@name = $currentEventName">
<xsl:set-variable name="anyMatched" value="1" />
implement guard/action
</xsl:if>
</xsl:if>
</xsl:for-each>
<xsl:if test="anyMatched=1">
throw new NotImplementedException();
</xsl>
Please note that I am unfamiliar with XSLT syntax; I hope you understand what I mean by “set-variable” even though it is not a real XSLT tag. I’m sure you can translate it into real XSLT.
Would it help to have two for-each
loops something like
<xsl:for-each select="//transition[trigger/@name = $currentEventName]">
<xsl:if test="@source = $currentStateName">
implement guard/action
</xsl:if>
</xsl:for-each>
<xsl:for-each select="//transition[not(some x in trigger/@name satisfies . = $currentEventName)]">
<xsl:if test="@source = $currentStateName">
throw new NotImplementedException();
</xsl:if>
</xsl:for-each>
?
At the risk of making myself unpopular, since you stated that XSLT doesn’t even have the most basic functionality of any reasonable programming language (namely, variables that you can set), my instinct would be to stop using it and to use something that you appear to be already familiar with — C#. So I translated your XSLT to C# and as far as I can tell it works just fine:
static void Main(string[] args)
{
Console.OutputEncoding = Encoding.UTF8;
Console.WriteLine(Transform(XDocument.Parse(xml)));
Console.ReadLine();
}
static string Transform(XDocument doc)
{
var topElem = doc.Root;
var fsmName = topElem.Attribute("name").Value;
var sb = new StringBuilder();
foreach (var state in topElem.Elements("state"))
{
var currentStateName = state.Attribute("name").Value;
sb.AppendLine(string.Format("public class {0} : {1}States", currentStateName, fsmName));
sb.AppendLine("{");
sb.AppendLine(string.Format(" public {0}()", currentStateName));
sb.AppendLine(" {");
sb.AppendLine(string.Format(@" Console.WriteLine(""In {0}State"");", currentStateName));
sb.AppendLine(" }");
foreach (var @event in topElem.Elements("event"))
{
var eventName = @event.Attribute("name").Value;
sb.AppendLine(string.Format(" public override void {0}(MediaPlayer player)", eventName));
sb.AppendLine(" {");
bool any = false;
foreach (var transition in topElem.Elements("transition"))
{
if (transition.Attribute("source").Value == currentStateName && transition.Element("trigger").Attribute("name").Value == eventName)
{
sb.AppendLine(" implement guard/action");
any = true;
break;
}
}
if (!any)
sb.AppendLine(" throw new NotImplementedException();");
sb.AppendLine(" }");
}
sb.AppendLine("}");
}
return sb.ToString();
}
Of course, the usual disclaimer applies: Please only re-use this code if you understand it. Make sure that the logic is in line with what you want it to do and make the necessary changes that suit your particular problem. For example, I’m not entirely sure that the “if” statement is entirely correct.
It is not clear to me what you are asking, but this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="vMatch" select="'MediaPlayerStates'"/>
<xsl:output method="text"/>
<xsl:key name="kTransitionBySource" match="transition" use="@source"/>
<xsl:template match="state">
<xsl:value-of select="concat('public class ',@name,' : MediaPlayerStates
',
'{
',
' public ',@name,'
',
' {
',
' Console.WriteLine("In ',@name,'State");
',
' }
')"/>
<xsl:apply-templates select="../event" mode="event">
<xsl:with-param name="pState" select="@name"/>
</xsl:apply-templates>
<xsl:text>}
</xsl:text>
</xsl:template>
<xsl:template match="event" mode="event">
<xsl:param name="pState"/>
<xsl:value-of select="concat(' public override void ',@name,'(MediaPlayer player)
',
' {
')"/>
<xsl:variable name="vTrigger" select="key('kTransitionBySource',$pState)/trigger"/>
<xsl:choose>
<xsl:when test="$vTrigger">
<xsl:value-of
select="concat(' implement guard/action (=> action="',$vTrigger/@action,'")
')"/>
</xsl:when>
<xsl:otherwise>
<xsl:text> throw new NotImplementedException();
</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:text> }
</xsl:text>
</xsl:template>
</xsl:stylesheet>
Output:
public class FastForward : MediaPlayerStates
{
public FastForward
{
Console.WriteLine("In FastForwardState");
}
public override void FastForwardButtonPressed(MediaPlayer player)
{
implement guard/action (=> action="playingSpeed * 2")
}
public override void PlayButtonPressed(MediaPlayer player)
{
implement guard/action (=> action="playingSpeed * 2")
}
}
public class PlayingMediaFile : MediaPlayerStates
{
public PlayingMediaFile
{
Console.WriteLine("In PlayingMediaFileState");
}
public override void FastForwardButtonPressed(MediaPlayer player)
{
throw new NotImplementedException();
}
public override void PlayButtonPressed(MediaPlayer player)
{
throw new NotImplementedException();
}
}
精彩评论