我有以下xml(简化结构)
<Workflow>
<CreateEntity />
<Activity Type="Custom" />
<Activity Type="Condition">
<Activity Type="ConditionBranch">
<UpdateEntity />
</Activity>
<Activity Type="ConditionBranch">
<Activity Type="Custom" />
</Activity>
</Activity>
<Activity Type="Custom" />
</Workflow>
并希望以这种方式改变它
<WorkflowProcess>
<Activities>
<!-- static start with known id -->
<Activity Id="StartId" />
<!-- activity from CreateEntity node; id - new GUID -->
<Activity Id="CreateEntityId" />
<!-- activity from Custom activity node; id - new GUID -->
<Activity Id="Custom1Id" />
<!-- so on -->
<Activity Id="ConditionId" />
<Activity Id="UpdateEntityId" />
<Activity Id="Custom2Id" />
<Activity Id="Custom3Id" />
<!-- static end with known id -->
<Activity Id="EndId" />
</Activities>
<Connections>
<Connection Id="new-guid" From="StartId" To="CreateEntityId"/>
<Connection Id="new-guid" From="CreateEntityId" To="Custom1Id"/>
<Connection Id="new-guid" From="Custom1Id" To="ConditionId"/>
<Connection Id="new-guid" From="ConditionId" To="UpdateEntityId"/>
<Connection Id="new-guid" From="ConditionId" To="Custom2Id"/>
<Connection Id="new-guid" From="UpdateEntityId" To="Custom3Id"/>
<Connection Id="new-guid" From="Custom2Id" To="Custom3Id"/>
<Connection Id="new-guid" From="Custom3Id" To="EndId"/>
</Connections>
</WorkflowProcess>
我已经写了最简单的部分 - 获取活动列表并坚持创建连接。
问题是如何构建通过新创建的Id引用我的活动的Connections节点?
我的样本XSL就像(简化)
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:myCustomCode="urn:myExtension">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="startActivityId" select="myCustomCode:NewId()"/>
<xsl:param name="endActivityId" select="myCustomCode:NewId()"/>
<xsl:template match="/">
<WorkflowProcess>
<Activities>
<!-- start -->
<Activity Id="{$startActivityId}" />
<xsl:apply-templates select="Workflow"/>
<!-- end -->
<Activity Id="{$endActivityId}" />
</Activities>
<Connections>
<!-- ???-->
<!-- how to compose these connections? -->
</Connections>
</WorkflowProcess>
</xsl:template>
<xsl:template match="Workflow">
<xsl:apply-templates select="CreateEntity"/>
<xsl:apply-templates select="UpdateEntity"/>
<xsl:apply-templates select="Activity[@Type='Custom']"/>
<xsl:apply-templates select="Activity[@Type='Condition']"/>
</xsl:template>
<xsl:template match="CreateEntity">
<xsl:variable name="activityId" select="myCustomCode:NewId()"/>
<Activity Id="{$activityId}" />
</xsl:template>
<xsl:template match="UpdateEntity">
<xsl:variable name="activityId" select="myCustomCode:NewId()"/>
<Activity Id="{$activityId}" />
</xsl:template>
<xsl:template match="Activity[@Type='Custom']">
<xsl:variable name="activityId" select="myCustomCode:NewId()"/>
<Activity Id="{$activityId}" />
</xsl:template>
<xsl:template match="Activity[@Type='Condition']">
<xsl:variable name="activityId" select="myCustomCode:NewId()"/>
<Activity Id="{$activityId}" />
<xsl:apply-templates select="Activity[@Type='ConditionBranch']"/>
</xsl:template>
<xsl:template match="Activity[@Type='ConditionBranch']">
<xsl:apply-templates select="Workflow"/>
</xsl:template>
</xsl:stylesheet>
更新1: 这是一个描述第一个/源xml(和目标也是)
的图表更新2: 尝试将连接规则形式化已经出现在这样的图表中(例如,添加了另一个活动)
更新3: 这是我的第一次尝试:在全局脚本对象中累积连接。由于XSLT中的变量是不可变的,我们无法修改它们,因此我使用全局对象来存储连接(请参阅脚本元素)。所以当我找到一个新活动时,我会在这里将它连接到全局对象。它允许我以连接方式递归构建所有活动。
修改XSL:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:myCustomCode="urn:myExtension"
exclude-result-prefixes="msxsl">
<msxsl:script implements-prefix="myCustomCode" language="C#">
<msxsl:using namespace="System.Text" />
<![CDATA[
public string NewId()
{
return Guid.NewGuid().ToString();
}
private StringBuilder _connections = new StringBuilder();
public void AppendConnection(string from, string to)
{
_connections.AppendFormat("<Connection Id='{0}' From='{1}' To='{2}' />", NewId(), from, to);
}
public string GetConnections()
{
return _connections.ToString();
}
]]>
</msxsl:script>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:param name="startActivityId" select="myCustomCode:NewId()"/>
<xsl:param name="endActivityId" select="myCustomCode:NewId()"/>
<xsl:template match="/">
<WorkflowProcess>
<Activities>
<!-- start -->
<Activity Id="{$startActivityId}" name="start" />
<xsl:apply-templates select="Workflow"/>
<!-- end -->
<Activity Id="{$endActivityId}" name="end" />
</Activities>
<Connections>
<!-- output connections from script global value -->
<xsl:value-of select="myCustomCode:GetConnections()" disable-output-escaping="yes" />
</Connections>
</WorkflowProcess>
</xsl:template>
<xsl:template match="Workflow | Activity[@Type='ConditionBranch']" name="Workflow">
<xsl:apply-templates select="CreateEntity"/>
<xsl:apply-templates select="UpdateEntity"/>
<xsl:apply-templates select="Activity[@Type='Custom']"/>
<xsl:apply-templates select="Activity[@Type='Condition']"/>
</xsl:template>
<xsl:template match="CreateEntity">
<xsl:variable name="activityId" select="myCustomCode:NewId()"/>
<Activity Id="{$activityId}" Type='CreateEntity' />
<!-- build connection to parent -->
<xsl:call-template name="buildConnection">
<xsl:with-param name="childId" select = "$activityId" />
</xsl:call-template>
</xsl:template>
<xsl:template match="UpdateEntity">
<xsl:variable name="activityId" select="myCustomCode:NewId()"/>
<Activity Id="{$activityId}" Type='UpdateEntity' />
<!-- build connection to parent -->
<xsl:call-template name="buildConnection">
<xsl:with-param name="childId" select = "$activityId" />
</xsl:call-template>
</xsl:template>
<xsl:template match="Activity[@Type='Custom']">
<xsl:variable name="activityId" select="myCustomCode:NewId()"/>
<Activity Id="{$activityId}" Type='Custom' />
<!-- build connection to parent -->
<xsl:call-template name="buildConnection">
<xsl:with-param name="childId" select = "$activityId" />
</xsl:call-template>
</xsl:template>
<xsl:template match="Activity[@Type='Condition']">
<xsl:variable name="activityId" select="myCustomCode:NewId()"/>
<Activity Id="{$activityId}" Type='Condition' />
<xsl:apply-templates select="Activity[@Type='ConditionBranch']"/>
<!-- build connection to parent -->
<xsl:call-template name="buildConnection">
<xsl:with-param name="childId" select = "$activityId" />
</xsl:call-template>
</xsl:template>
<!-- find parent and add connection to global script variable -->
<xsl:template name="buildConnection">
<xsl:param name = "childId" />
<!-- the main trick is to get parent id here and pass to my custom function -->
<xsl:apply-templates select="myCustomCode:AppendConnection($childId, 'parentId')"/>
</xsl:template>
</xsl:stylesheet>
答案 0 :(得分:0)
我做了以下尝试来实施您的规则。在进一步移动之前,我想知道在您可能拥有的任何场景中,这是否正确生成了所需的连接。当条件嵌套时,我怀疑它可能不会。
<强> XML 强>
<Workflow>
<CreateEntity name="A"/>
<Activity name="B" Type="Custom" />
<Activity name="C" Type="Condition">
<Activity name="C1" Type="ConditionBranch">
<UpdateEntity name="C1A" />
</Activity>
<Activity name="C2" Type="ConditionBranch">
<Activity name="C2A" Type="Custom" />
<Activity name="C2B" Type="Custom" />
</Activity>
</Activity>
<Activity name="D" Type="Custom" />
</Workflow>
<强> XSLT 强>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/Workflow">
<WorkflowProcess>
<Activities/>
<Connections>
<xsl:apply-templates select="*" mode="conections"/>
</Connections>
</WorkflowProcess>
</xsl:template>
<xsl:template match="*" mode="conections">
<xsl:choose>
<xsl:when test="self::CreateEntity">
<Connection From="0" To="{@name}"/>
</xsl:when>
<xsl:when test="preceding-sibling::*[1]/@Type='Condition'">
<xsl:variable name="name" select="@name" />
<xsl:for-each select="preceding-sibling::*[1]/*/*[last()]">
<Connection From="{@name}" To="{$name}"/>
</xsl:for-each>
</xsl:when>
<xsl:when test="@Type='ConditionBranch'"/>
<xsl:when test="not(preceding-sibling::*)">
<Connection From="{ancestor::*[2]/@name}" To="{@name}"/>
</xsl:when>
<xsl:otherwise>
<Connection From="{preceding-sibling::*[1]/@name}" To="{@name}"/>
</xsl:otherwise>
</xsl:choose>
<xsl:apply-templates mode="conections"/>
</xsl:template>
</xsl:stylesheet>
<强>结果强>
<?xml version="1.0" encoding="UTF-8"?>
<WorkflowProcess>
<Activities/>
<Connections>
<Connection From="0" To="A"/>
<Connection From="A" To="B"/>
<Connection From="B" To="C"/>
<Connection From="C" To="C1A"/>
<Connection From="C" To="C2A"/>
<Connection From="C2A" To="C2B"/>
<Connection From="C1A" To="D"/>
<Connection From="C2B" To="D"/>
</Connections>
</WorkflowProcess>
答案 1 :(得分:0)
虽然最终的解决方案非常复杂,但简化版本是:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:myCustomCode="urn:myExtension"
exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<msxsl:script implements-prefix="myCustomCode" language="C#">
<msxsl:using namespace="System.Collections.Generic" />
<![CDATA[
public string NewId()
{
return Guid.NewGuid().ToString();
}
private Dictionary<string, string> _ids = new Dictionary<string, string>();
/* Converts XSL generated ids to GUIDs */
public string GetId(string xsl_id)
{
if (!_ids.ContainsKey(xsl_id))
{
_ids.Add(xsl_id, NewId());
}
return _ids[xsl_id];
}
]]>
</msxsl:script>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:param name="startActivityId" select="myCustomCode:NewId()"/>
<xsl:param name="endActivityId" select="myCustomCode:NewId()"/>
<xsl:template match="/">
<WorkflowProcess>
<Activities>
<!-- start -->
<Activity Id="{$startActivityId}" name="start" />
<xsl:apply-templates select="Workflow"/>
<!-- end -->
<Activity Id="{$endActivityId}" name="end" />
</Activities>
<Connections>
<xsl:apply-templates select="*" mode="conections"/>
</Connections>
</WorkflowProcess>
</xsl:template>
<xsl:template match="Workflow | Activity[@Type='ConditionBranch']" name="Workflow">
<xsl:apply-templates select="CreateEntity"/>
<xsl:apply-templates select="UpdateEntity"/>
<xsl:apply-templates select="Activity[@Type='Custom']"/>
<xsl:apply-templates select="Activity[@Type='Condition']"/>
</xsl:template>
<xsl:template match="CreateEntity">
<xsl:variable name="activityId" select="myCustomCode:GetId(generate-id())"/>
<Activity Id="{$activityId}" Type='CreateEntity' name="{@name}" />
</xsl:template>
<xsl:template match="UpdateEntity">
<xsl:variable name="activityId" select="myCustomCode:GetId(generate-id())"/>
<Activity Id="{$activityId}" Type='UpdateEntity' name="{@name}" />
</xsl:template>
<xsl:template match="Activity[@Type='Custom']">
<xsl:variable name="activityId" select="myCustomCode:GetId(generate-id())"/>
<Activity Id="{$activityId}" Type='Custom' name="{@name}" />
</xsl:template>
<xsl:template match="Activity[@Type='Condition']">
<xsl:variable name="activityId" select="myCustomCode:GetId(generate-id())"/>
<Activity Id="{$activityId}" Type='Condition' name="{@name}" />
<xsl:apply-templates select="Activity[@Type='ConditionBranch']"/>
</xsl:template>
<!-- Connections -->
<xsl:template match="CreateEntity | UpdateEntity | Activity[@Type='Custom'] | Activity[@Type='Condition']" mode="conections">
<!-- attach the first element to starting activity -->
<xsl:if test="local-name(parent::node()) = 'Workflow' and position() = 1">
<Connection From="{$startActivityId}" To="{myCustomCode:GetId(generate-id(.))}"/>
</xsl:if>
<!-- a first element in a condition branch attached to the condition -->
<xsl:if test="parent::node()/@Type='ConditionBranch' and position() = 1">
<Connection From="{myCustomCode:GetId(generate-id(ancestor::*[2]))}" To="{myCustomCode:GetId(generate-id(.))}"/>
</xsl:if>
<!-- the last element attached to terminator (ending activity) -->
<xsl:if test="local-name(parent::node()) = 'Workflow' and position() = last()">
<xsl:choose>
<!-- if a last element in workflow is condition attach every its last activities to terminator -->
<xsl:when test="@Type='Condition'">
<!-- select only last leaf-nodes of the condition -->
<xsl:for-each select="descendant::*/*[last()][not(child::*)]">
<Connection From="{myCustomCode:GetId(generate-id(.))}" To="{$endActivityId}"/>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<Connection From="{myCustomCode:GetId(generate-id(.))}" To="{$endActivityId}"/>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
<xsl:choose>
<!-- if previous element is Condition, attach to its last activities -->
<xsl:when test="preceding-sibling::*[1]/@Type='Condition'">
<xsl:variable name="childId" select="myCustomCode:GetId(generate-id(.))" />
<xsl:for-each select="preceding::*/*[last()][not(child::*)]">
<Connection From="{myCustomCode:GetId(generate-id(.))}" To="{$childId}"/>
</xsl:for-each>
</xsl:when>
<!-- attach to preceding sibling -->
<xsl:otherwise>
<xsl:if test="position() > 1">
<Connection From="{myCustomCode:GetId(generate-id(preceding-sibling::*[1]))}" To="{myCustomCode:GetId(generate-id(.))}"/>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="@Type='Condition'">
<xsl:apply-templates mode="conections"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
注意,generate-id()
函数在转换期间始终为特定节点返回相同的id。
另一点要注意如何将条件子活动连接到以下条件兄弟元素或终止符。
最后一个:我使用字典将生成的XSL ID转换为脚本块中的GUID。
非常感谢@ michael.hor257k的帮助。