2016-07-22 154 views
0

我有以下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圖表(和目標太)

sample diagram

更新2: 試圖形式化連接規則已經到了這樣的圖表(與增加了另一個交流tivity例如)

enter image description here

更新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中的活動從上到下排列,因此它們不需要id和連接。目標表單必須具有Id和連接。 – Vladislav

+0

我想可以恢復從子元素向後移動的連接:對於第一個活動父元素是開始元素(該id是已知的)。 – Vladislav

回答

0

雖然最終的解決方案非常bacame複雜的,簡化的版本是:

<?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標識符轉換爲腳本塊中的GUID。

非常感謝@ michael.hor257k的幫助。

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

謝謝你的幫助,邁克爾。不幸的是,輸入XML在活動節點上沒有名稱或任何其他ID,我必須在XSL中生成並分配它們的ID。也有很多嵌套的條件,所以我使用遞歸來遍歷樹。請檢查問題中的更新3。 – Vladislav

+0

「*輸入XML沒有名稱*」我很清楚這一點,但我需要這些名稱用於測試目的 - 否則我將無法判斷結果是否正確。一旦你有正確的規則實施,從姓名切換到ID是微不足道的。 - 「*還有很多嵌套的條件,*」我問你說明規則。 –

+0

請原諒我不太清楚,條件是一種活動,它可以嵌套到其他條件。 您的XSL正確生成所需的連接,直到沒有嵌套條件。 至於切換到ID - 這對我來說不是微不足道的,因爲輸入XML沒有名稱。 – Vladislav