【问题标题】:How to break a for-each loop when value is set to prevent a double evaluation?如何在设置值以防止双重评估时打破 for-each 循环?
【发布时间】:2011-03-31 20:11:31
【问题描述】:

更新:改进了解释,希望问题现在很清楚:)

大家好!

我有一个 XML,其中声明了 FSM 的转换(以及事件和状态偏离)。

这是 XML 的简化版本,它也会导致下面描述的问题:

<?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>

我的目标是使用 XSLT 在 C# 中生成 FSM。 FSM 的实现必须符合状态模式。因此,我需要为每个州创建一个类。在每个类中,方法将代表事件。由于这些类派生自一个包含所有方法(事件)的抽象类,因此每个类/状态都需要实现所有方法(事件)。当然并不是所有的方法都被实际实现,因为一个状态没有使用所有的事件。因此,我选择以异常方式实现未使用的事件。

为了生成 C# 代码,我需要遍历 XML 并为每个状态生成一个类。创建类后,下一步是为每个事件生成一个方法。目前,这是困难的部分。对于每个方法(事件),我需要检查我正在评估的当前状态是否存在事件。因此,我遍历 XML 并寻找属性“源”等于当前评估的状态/类的转换。如果找到一个,我检查触发子项是否与我正在评估的当前事件相同。如果两个测试都为真,则“transtion/trigger/@action”和“transtion/trigger/@guard”的内容需要用作当前方法/事件的实现。如果没有找到有效的转换,则表示该状态的事件不存在,因此必须作为异常实现。

目前我还没有编写用于读取动作和保护属性的代码,因此不会为此生成代码。现在,当转换对当前状态和事件有效时,我使用文本“实现保护/操作”,并使用文本“抛出新的 NotImplementedException();”当没有找到转换时。或者说,应该这样做……

我编写的代码几乎可以让我到达那里:

<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>

现在这段代码的输出是:

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)
  {

  }
}

正如您可能理解的那样,这是错误的。在“FastForward”类中生成错误。它只需要“实现保护/动作”,而不需要“抛出新的 NotImplementedException();”。在“PlayingMediaFile”类中,没有生成任何内容,而需要用“throw new NotImplementedException();”填充因为那个类没有事件。

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();
  }
}

现在我明白为什么会出错了,我将尝试解释:当循环所有转换时,它会遇到两个转换。第一个将对测试进行评估,因此它会生成“实施警卫/操作”文本。现在它遍历到评估为假的第二个转换节点。由于错误评估,它会生成 NotImplementedException()。这发生在同一事件中,因此是错误的。

所以,据我所知,当测试为真时,for-each 循环的转换需要中断。如果没有找到匹配的转换,则需要生成“NotImplementedException”文本。

我尝试了各种方法,但我无法完成。有人可以帮帮我吗?

谢谢

【问题讨论】:

  • @r0h: 这一点我都不清楚。期望的输出是什么?
  • 当被评估的状态和事件有相应的转换时,需要生成“实现保护/动作”。没有异常时需要生成异常。现在问题都是由于双重评估而产生的。这是因为有两个过渡节点。因此它需要在有有效测试时退出 for each 循环,并在没有有效测试时生成“实施保护/动作”或生成异常。
  • r0h:我认为您在这里有交叉引用,但您的问题使这一点不清楚。检查我的答案。

标签: c# xml xslt code-generation foreach


【解决方案1】:

有两个&lt;transition&gt; 元素。因此,这个循环:

<xsl:for-each select="//transition">

将有两次迭代。两次迭代之一生成您的implement guard/action,另一个生成异常代码。

也许你的意思是这样的? (使用伪 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>

请注意,我不熟悉 XSLT 语法;我希望你能理解我所说的“set-variable”是什么意思,即使它不是真正的 XSLT 标记。我相信你可以把它翻译成真正的 XSLT。

【讨论】:

  • 从 C# 的角度来看,我也尝试了类似的方法。这里的问题是不能给变量一个新值。这在 XSLT 中是不可能的。省略第一个声明也是不可能的,因为它将超出范围......
  • XSLT 中没有什么比 set-variable 更好的了。变量 - 不像它们的名字所暗示的那样 - 是不可变的。您只能在声明时设置变量的值。
【解决方案2】:

有两个类似for-each 的循环会有所帮助吗

<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>

?

【讨论】:

  • 我不太了解 de for-each 循环中的 select 语句。你能详细说明一下吗?
  • XSLT for-each 元素的 select 属性是一个 XPath 表达式,返回循环运行的节点集。 XPath 允许您使用方括号中的条件,该条件应用于节点集的每个节点以过滤节点。这或多或少类似于 SQL 或 LINQ 中的 WHERE 条件。因此,实际上第一个循环仅在具有与 $currentEventName 匹配的 name 属性的 trigger 元素的转换上运行。
  • 好的,同时我自己也想出来了(对不起:P)。第二个选择语句对我来说仍然不清楚。第一个选择具有当前状态的转换,但第二个,将在那里选择什么?或许你可以在伪类的基础上再实现一点?
  • 第二个循环沿着没有名称属性为 $currentEventName 的触发器子元素的转换循环,正如我从您在问题中的描述中了解到的那样,只有这些,您才想抛出异常。但是,我不确定是否不需要将 if 条件合并到两个 for 循环选择中。
  • 在这里失去你。觉得累了我已经添加了我正在寻找的结果,所以也许可以解决问题。我明天会回来。感谢您迄今为止的支持!
【解决方案3】:

冒着让自己不受欢迎的风险,因为你说 XSLT 甚至没有任何合理的编程语言的最基本功能(即你可以设置的变量),我的直觉是停止使用它并使用你似乎已经熟悉的东西——C#。所以我将你的 XSLT 翻译成 C#,据我所知它工作得很好:

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();
}

当然,通常的免责声明适用:请仅在您理解此代码的情况下重复使用。确保逻辑符合您希望它执行的操作,并根据您的特定问题进行必要的更改。例如,我不完全确定“if”语句是否完全正确。

【讨论】:

  • 嗯,XSLT 是一种函数式语言,而不是过程式语言。函数式语言的一个特点是操作没有副作用,即。 e.一次或多次调用相同的操作不会改变结果。这使优化和并行化变得容易。它不是唯一的函数式语言:Haskell、F#,在某种程度上 SQL SELECT 语句是其他流行的函数式语言。你只需要习惯函数式语言的不同思维。
  • 将上面的 C# 代码也改成纯函数式并不难。可以使用.Select() 迭代器和.Any() 调用,而不是设置bool any 变量的foreach 外观。也许有与其中任何一个等效的 XSLT?
  • XSLT 可能缺少很多东西,但它仍然是图灵完备的 :)
【解决方案4】:

我不清楚你在问什么,但是这个样式表:

<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&#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>

输出:

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 的努力,但我认为现在有点过头了。我也怕我看不懂你的代码。也许您可以稍微简化一下,因为我试图改进对我的问题的解释。希望你现在明白了:)
  • r0h:我不知道您所说的“过度”是什么意思...我看到现在您添加了更完整的所需输出...结果就像我自己的一样.所以,看来我已经明白了!说明:对于每一个state 应用模板为每一个event 传递state/@name 作为$pState 参数;然后,对于每个event 输出操作,如果有一个transition@source 等于$pSate 和一个trigger 孩子(我开始认为可能不会有trigger),否则输出例外。
猜你喜欢
  • 1970-01-01
  • 2014-09-10
  • 1970-01-01
  • 1970-01-01
  • 2020-04-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-18
相关资源
最近更新 更多