使用 XSLT 将节点结构保持为连接元素的扁平树视图结构
Flattened tree view structure with XSLT keeping nodes structure as connections elements
我有以下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 引用我的活动的连接节点?
我的示例 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:
这是描述 first/source xml(以及目标)
的图表
更新二:
尝试将连接规则形式化到这样的图表(例如添加另一个 activity)
更新 3:
这是我的第一次尝试:在全局脚本对象中累积连接。由于 XSLT 中的变量是不可变的,我们无法修改它们,因此我使用全局对象来存储连接(请参阅脚本元素)。因此,当我找到一个新的 activity 时,我就在此处将其添加到全局对象。它允许我递归地构建所有具有连接的活动。
修改后的 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>
我做了以下尝试来实施您的规则。在继续之前,我想知道在您可能遇到的任何情况下,这是否正确生成了所需的连接。我怀疑当条件嵌套时可能不会。
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>
虽然最终的解决方案变得非常复杂,但简化版本是:
<?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()
在转换过程中始终为特定节点运行 returns 相同的 id。
另一点要注意如何将条件子活动连接到后续条件同级元素或终止符。
最后一个:我使用字典将生成的 XSL id 转换为脚本块中的 GUID。
非常感谢@michael.hor257k 的帮助。
我有以下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 引用我的活动的连接节点?
我的示例 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: 这是描述 first/source xml(以及目标)
的图表更新二: 尝试将连接规则形式化到这样的图表(例如添加另一个 activity)
更新 3: 这是我的第一次尝试:在全局脚本对象中累积连接。由于 XSLT 中的变量是不可变的,我们无法修改它们,因此我使用全局对象来存储连接(请参阅脚本元素)。因此,当我找到一个新的 activity 时,我就在此处将其添加到全局对象。它允许我递归地构建所有具有连接的活动。
修改后的 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>
我做了以下尝试来实施您的规则。在继续之前,我想知道在您可能遇到的任何情况下,这是否正确生成了所需的连接。我怀疑当条件嵌套时可能不会。
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>
虽然最终的解决方案变得非常复杂,但简化版本是:
<?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()
在转换过程中始终为特定节点运行 returns 相同的 id。
另一点要注意如何将条件子活动连接到后续条件同级元素或终止符。
最后一个:我使用字典将生成的 XSL id 转换为脚本块中的 GUID。
非常感谢@michael.hor257k 的帮助。