views:

108

answers:

4

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 NotImplementedException();" 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

A: 

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.

Timwi
From a C# point of view I also tried something likewise. The problem here is that a variable cannot be given a new value. This isn't possible in XSLT. Omitting the first declaration is also not possible since it will be out of scope...
There is nothing like a set-variable in XSLT. Variables are - unlike their name suggests - immutable. You can only set the value of a variable at declaration time.
Frank
A: 

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>

?

Frank
I don't quite understand the select statements in de for-each loop. Could you elaborate a bit more about them?
The select attribute of the XSLT for-each element is an XPath expression returning a node set that the loop runs along. And XPath allows you to use a conditions in square brackets that are applied on each node of a node set to filter the nodes. This is more or less like a WHERE condition in SQL or LINQ.Hence in effect the first loop only runs on transitions having a trigger element with a name attribute matching the $currentEventName.
Frank
Ok, figured that out myself in the meantime (sorry :P). The second select statement is still unclear to me though. The first one selects the transitions which have the currentstate, but the second one, what will be selected there? Perhaps you can implement it a little more instaed of the psuedo?
The second loop looops along transitions not having a trigger child element with a name attribute of $currentEventName, as I understood from your description in the question that only for these, you would want to throw the Exception.I am not absolutely sure, however, if you would not need to combine even the if condition into both for loop selects.
Frank
Loosing you here. Getting tired I think. I've added the result I'm looking for so perhaps that clears things up. I'll be back tomorrow. Thanks for the support so far!
A: 

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.

Timwi
Well, XSLT is a functional language, not a procedural one. And it is a characteristic of functional languages that operations are side-effect free, i. e. calling the same action one or more time does not change the result. This enables easy optimization and parallelization. It is not the only functional language: Haskell, F#, to some extent SQL SELECT statements are other popular functional languages. You just have to get used to the different thinking of functional languages.
Frank
It is not too hard to change the above C# code to be purely functional too. Instead of a `foreach` look that sets the `bool any` variable, one could use a `.Select()` iterator and an `.Any()` call. Maybe there is an XSLT equivalent to either of those?
Timwi
XSLT might lack a lot, but it is Turing-complete nonetheless :)
Jakob
A: 

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"&gt;
    <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&#xA;',
                                     '{&#xA;',
                                     '  public ',@name,'&#xA;',
                                     '  {&#xA;',
                                     '      Console.WriteLine(&quot;In ',@name,'State&quot;);&#xA;',
                                     '  }&#xA;')"/>
        <xsl:apply-templates select="../event" mode="event">
            <xsl:with-param name="pState" select="@name"/>
        </xsl:apply-templates>
        <xsl:text>}&#xA;</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)&#xA;',
                                     '  {&#xA;')"/>
        <xsl:variable name="vTrigger" select="key('kTransitionBySource',$pState)/trigger"/>
        <xsl:choose>
            <xsl:when test="$vTrigger">
                <xsl:value-of
                      select="concat('            implement guard/action  (=> action=&quot;',$vTrigger/@action,'&quot;)&#xA;')"/>
            </xsl:when>
            <xsl:otherwise>
                            <xsl:text>            throw new NotImplementedException();&#xA;</xsl:text>
            </xsl:otherwise>
        </xsl:choose>
                            <xsl:text>  }&#xA;</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();
  }
}
Alejandro
Thanks for your effort Alejandro but I think its a little overdone right now. I'm also afraid I don't understand your code. Perhaps you can simplify it a bit because I tried to improve the explanation of my problem. Hope you understand it now :)
r0h: I don't know what you mean by "overdone"... I see that now you have added a more complete desired output... and it has resulted to be just like my own. So, it looks like I have understood! Explanation: for every `state` apply templates for every `event` passing `state/@name` as `$pState` param; then, for every `event` output action if there is a `transition` with `@source` equal to `$pSate` and a `trigger` child (I came to think that maybe there would not be a `trigger`), otherwise output the exception.
Alejandro