How to replace SplitBlock and associated InputPort + OutputPort + Link tags with Link tags only?

This is a followup question to parse XML without using global variables

I want to parse this kind of XML using XSLT 1.0 only:

<root>
  <!-- example 1 - no SplitBlock tag -->
  <Block id="1" inputPorts="0" outputPorts="1" />
  <OutputPort id="2" parent="1" />
  <Block id="3" inputPorts="1" outputPorts="0" />
  <InputPort id="4" parent="3" />
  <Link id="5" source="2" target="4" />

  <!-- example 2 - one SplitBlock tag -->
  <Block id="6" inputPorts="0" outputPorts="1" />
  <OutputPort id="7" parent="6" />
  <Block id="8" inputPorts="1" outputPorts="0" />
  <InputPort id="9" parent="8" />
  <Block id="10" inputPorts="1" outputPorts="0" />
  <InputPort id="11" parent="10" />
  <SplitBlock id="old-12" inputPorts="1" outputPorts="2" />
  <InputPort id="old-13" parent="old-12" />
  <OutputPort id="old-14" parent="old-12" />
  <OutputPort id="old-15" parent="old-12" />
  <Link id="old-16" source="7" target="old-13" />
  <Link id="old-17" source="old-14" target="9" />
  <Link id="old-18" source="old-15" target="11" />

  <!-- example 3 - two connected SplitBlock tags -->
  <Block id="19" inputPorts="0" outputPorts="1" />
  <OutputPort id="20" parent="19" />
  <Block id="21" inputPorts="1" outputPorts="0" />
  <InputPort id="22" parent="21" />
  <Block id="23" inputPorts="0" outputPorts="1" />
  <OutputPort id="24" parent="23" />
  <Block id="25" inputPorts="1" outputPorts="0" />
  <InputPort id="26" parent="25" />
  <SplitBlock id="old-27" inputPorts="1" outputPorts="2" />
  <InputPort id="old-28" parent="old-27" />
  <OutputPort id="old-29" parent="old-27" />
  <OutputPort id="old-30" parent="old-27" />
  <SplitBlock id="old-31" inputPorts="2" outputPorts="1" />
  <InputPort id="old-32" parent="old-31" />
  <InputPort id="old-33" parent="old-31" />
  <OutputPort id="old-34" parent="old-31" />
  <Link id="old-35" source="20" target="old-28" />
  <Link id="old-36" source="old-29" target="22" />
  <Link id="old-37" source="old-30" target="old-32" />
  <Link id="old-38" source="24" target="old-33" />
  <Link id="old-39" source="old-34" target="26" />
</root>

There are multple Block and SplitBlock tags. InputPort and OutputPort tags follow in any order after the Block or SplitBlock tag, as per number available in attributes of Block and SplitBlock tags. In addition, a SplitBlock has either exactly two InputPort tags and one OutputPort tag or one InputPort tag and two OutputPort tags. Also, there are Link tags which connect an OutputPort (which is the source attribute for the Link) to an InputPort (which is the target attribute of the Link) of either Block or SplitBlock. More importantly, a Link is not connected to another Link.

Each Block, SplitBlock, InputPort, OutputPort, and Link tag has a unique id. All tags which should be removed by this transformation are marked with old- in the id attribute.

The expected output is like this:

<root>
  <!-- example 1 - no SplitBlock tag -->
  <Block id="1" inputPorts="0" outputPorts="1" />
  <OutputPort id="2" parent="1" coordinate="0.5" />
  <Block id="3" inputPorts="1" outputPorts="0" />
  <InputPort id="4" parent="3" coordinate="0.5" />
  <Link id="5" source="2" target="4" />

  <!-- example 2 - one SplitBlock tag -->
  <Block id="6" inputPorts="0" outputPorts="1" />
  <OutputPort id="7" parent="6" coordinate="0.5" />
  <Block id="8" inputPorts="1" outputPorts="0" />
  <InputPort id="9" parent="8" coordinate="0.5" />
  <Block id="10" inputPorts="1" outputPorts="0" />
  <InputPort id="11" parent="10" coordinate="0.5" />
  <Link id="new-40" source="7" target="9" />
  <Link id="new-41" source="new-40" target="11" />

  <!-- example 3 - two connected SplitBlock tags -->
  <Block id="19" inputPorts="0" outputPorts="1" />
  <OutputPort id="20" parent="19" coordinate="0.5" />
  <Block id="21" inputPorts="1" outputPorts="0" />
  <InputPort id="22" parent="21" coordinate="0.5" />
  <Block id="23" inputPorts="0" outputPorts="1" />
  <OutputPort id="24" parent="23" coordinate="0.5" />
  <Block id="25" inputPorts="1" outputPorts="0" />
  <InputPort id="26" parent="25" coordinate="0.5" />
  <Link id="new-42" source="20" target="22" />
  <Link id="new-43" source="new-42" target="26" />
  <Link id="new-44" source="24" target="new-43" />
</root>

The aim of this transformation is to replace all SplitBlock tags, its attached InputPort and OutputPort tags, and its attached Link tags with Link tags only. Note that now, Link tags may be connected to other Link tags also along with InputPort and OutputPort tags.

All tags which should be added by this transformation are marked with new- in the id attribute. I tried using generate-id(), but that did not work as expected. I now know that both calls to generate-id() are in the context of the same SplitBlock tag and that can be fixed by passing a different node, but I am not clear how to get a different node.

Visually, the transformation can be explained by these diagrams. The number in the diagrams is their id in the above examples (I have dropped the old- and new- prefixes):

Example 2

Example 3

The Block tags are represented by the big rectangles and the SplitBlock tags are represented by the medium rectangles. The small squares represent the OutputPort tags when on the right of the Block or the SplitBlock and the InputPort tags when on the left. The Link tag is represented by the line between an OutputPort (which is a source for the Link) and an InputPort (which is a target for the Link). In the output, one of the two can be replaced by another Link.

Updated code:

I modified the xsl file from the previous answer by @michael.hor257k. I have found an answer which works correctly for one SplitBlock tag, but not for two connected SplitBlock tags. Here is my effort:

<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:key name="k-in" match="InputPort" use="@parent" />
  <xsl:key name="k-out" match="OutputPort" use="@parent" />
  <xsl:key name="k-link-target" match="Link" use="@target" />
  <xsl:key name="k-link-source" match="Link" use="@source" />
  <xsl:key name="k-input" match="InputPort" use="@id" />
  <xsl:key name="k-output" match="OutputPort" use="@id" />
  <xsl:key name="k-block" match="Block | SplitBlock" use="@id" />

  <xsl:template match="/root">
    <xsl:copy>
      <xsl:for-each select="Block">
        <xsl:variable name="inputPorts" select="@inputPorts" />
        <xsl:variable name="outputPorts" select="@outputPorts" />
        <xsl:copy-of select="." />
        <!-- input ports -->
        <xsl:for-each select="key('k-in', @id)">
          <xsl:copy>
            <xsl:copy-of select="@*" />
            <xsl:variable name="numerator" select="2 * (position() - 1) + 1" />
            <xsl:attribute name="coordinate">
              <xsl:value-of select="$numerator div (2 * $inputPorts)" />
            </xsl:attribute>
          </xsl:copy>
        </xsl:for-each>
        <!-- output ports -->
        <xsl:for-each select="key('k-out', @id)">
          <xsl:copy>
            <xsl:copy-of select="@*" />
            <xsl:variable name="numerator" select="2 * (position() - 1) + 1" />
            <xsl:attribute name="coordinate">
              <xsl:value-of select="$numerator div (2 * $outputPorts)" />
            </xsl:attribute>
          </xsl:copy>
        </xsl:for-each>
      </xsl:for-each>

      <xsl:for-each select="SplitBlock">
        <xsl:variable name="inputPorts" select="@inputPorts" />
        <xsl:variable name="outputPorts" select="@outputPorts" />
        <xsl:variable name="inputPortList" select="key('k-in', @id)" />
        <xsl:variable name="outputPortList" select="key('k-out', @id)" />
        <xsl:variable name="inputPort" select="$inputPortList[position() = 1]" />
        <xsl:variable name="outputPort" select="$outputPortList[position() = 1]" />
        <xsl:variable name="targetLink" select="key('k-link-target', $inputPort/@id)" />
        <xsl:variable name="sourceLink" select="key('k-link-source', $outputPort/@id)" />
        <xsl:variable name="newid1" select="generate-id()" />
        <xsl:variable name="newid2" select="generate-id($inputPort)" />
        <Link>
          <xsl:attribute name="id">
            <xsl:value-of select="$newid1" />
          </xsl:attribute>
          <xsl:attribute name="source">
            <xsl:value-of select="$targetLink/@source" />
          </xsl:attribute>
          <xsl:attribute name="target">
            <xsl:value-of select="$sourceLink/@target" />
          </xsl:attribute>
        </Link>
        <xsl:choose>
          <xsl:when test="$inputPorts = 2 and $outputPorts = 1">
            <xsl:variable name="inputPort2" select="$inputPortList[position() = 2]" />
            <xsl:variable name="target2Link" select="key('k-link-target', $inputPort2/@id)" />
            <Link>
              <xsl:attribute name="id">
                <xsl:value-of select="$newid2" />
              </xsl:attribute>
              <xsl:attribute name="source">
                <xsl:value-of select="$target2Link/@source" />
              </xsl:attribute>
              <xsl:attribute name="target">
                <xsl:value-of select="$newid1" />
              </xsl:attribute>
            </Link>
          </xsl:when>
          <xsl:when test="$inputPorts = 1 and $outputPorts = 2">
            <xsl:variable name="outputPort2" select="$outputPortList[position() = 2]" />
            <xsl:variable name="source2Link" select="key('k-link-source', $outputPort2/@id)" />
            <Link>
              <xsl:attribute name="id">
                <xsl:value-of select="$newid2" />
              </xsl:attribute>
              <xsl:attribute name="source">
                <xsl:value-of select="$newid1" />
              </xsl:attribute>
              <xsl:attribute name="target">
                <xsl:value-of select="$source2Link/@target" />
              </xsl:attribute>
            </Link>
          </xsl:when>
          <xsl:otherwise>
            <Error>
              <xsl:attribute name="inputPorts">
                <xsl:value-of select="$inputPorts" />
              </xsl:attribute>
              <xsl:attribute name="outputPorts">
                <xsl:value-of select="$outputPorts" />
              </xsl:attribute>
            </Error>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:for-each>

      <xsl:for-each select="Link">
        <xsl:variable name="sourcePort" select="key('k-output', @source)" />
        <xsl:variable name="targetPort" select="key('k-input', @target)" />
        <xsl:variable name="sourceBlock" select="key('k-block', $sourcePort/@parent)" />
        <xsl:variable name="targetBlock" select="key('k-block', $targetPort/@parent)" />
        <xsl:if test="name($sourceBlock) != 'SplitBlock' and name($targetBlock) != 'SplitBlock'">
          <xsl:copy-of select="." />
        </xsl:if>
      </xsl:for-each>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

For Example 2, it gives the correct output as:

  <Link id="idm13" source="7" target="9"/>
  <Link id="idm14" source="idm13" target="11"/>

However, for Example 3, it gives 4 Link tags instead of 3:

  <Link id="idm28" source="20" target="22"/>
  <Link id="idm29" source="idm28" target="old-32"/>
  <Link id="idm32" source="old-30" target="26"/>
  <Link id="idm33" source="24" target="idm32"/>

The correct output should have only 3 Link tags:

  <Link id="idm28" source="20" target="22"/>
  <Link id="idm32" source="idm28" target="26"/>
  <Link id="idm33" source="24" target="idm32"/>

Please help on this now.

My old code below:

<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:key name="k-in" match="InputPort" use="@parent" />
  <xsl:key name="k-out" match="OutputPort" use="@parent" />

  <xsl:template match="/root">
    <xsl:copy>
      <xsl:for-each select="Block">
        <xsl:variable name="inputPorts" select="@inputPorts" />
        <xsl:variable name="outputPorts" select="@outputPorts" />
        <xsl:copy-of select="." />
        <!-- input ports -->
        <xsl:for-each select="key('k-in', @id)">
          <xsl:copy>
            <xsl:copy-of select="@*" />
            <xsl:variable name="numerator" select="2 * (position() - 1) + 1" />
            <xsl:attribute name="coordinate">
              <xsl:value-of select="$numerator div (2 * $inputPorts)" />
            </xsl:attribute>
          </xsl:copy>
        </xsl:for-each>
        <!-- output ports -->
        <xsl:for-each select="key('k-out', @id)">
          <xsl:copy>
            <xsl:copy-of select="@*" />
            <xsl:variable name="numerator" select="2 * (position() - 1) + 1" />
            <xsl:attribute name="coordinate">
              <xsl:value-of select="$numerator div (2 * $outputPorts)" />
            </xsl:attribute>
          </xsl:copy>
        </xsl:for-each>
      </xsl:for-each>

      <xsl:for-each select="SplitBlock">
        <xsl:variable name="inputPorts" select="@inputPorts" />
        <xsl:variable name="outputPorts" select="@outputPorts" />
        <xsl:variable name="newid1" select="generate-id()" />
        <xsl:variable name="source1id" select="..." /> <!-- TODO: get first source id connected to output port of SplitBlock -->
        <xsl:variable name="target1id" select="..." /> <!-- TODO: get first target id connected to input port of SplitBlock -->
        <Link>
          <xsl:attribute name="id">
            <xsl:value-of select="$newid1" />
          </xsl:attribute>
          <xsl:attribute name="source">
            <xsl:value-of select="$source1id" />
          </xsl:attribute>
          <xsl:attribute name="target">
            <xsl:value-of select="$target1id" />
          </xsl:attribute>
        </Link>
        <xsl:choose>
          <xsl:when test="$inputPorts = 2 and $outputPorts = 1">
            <xsl:variable name="target2id" select="..." /> <!-- TODO: get second target id connected to input port of SplitBlock -->
            <Link>
              <xsl:attribute name="id">
                <xsl:value-of select="generate-id()" /> <!-- FIXME: this generates same id as $newid1 -->
              </xsl:attribute>
              <xsl:attribute name="source">
                <xsl:value-of select="$newid1" />
              </xsl:attribute>
              <xsl:attribute name="target">
                <xsl:value-of select="$target2id" />
              </xsl:attribute>
            </Link>
          </xsl:when>
          <xsl:when test="$inputPorts = 1 and $outputPorts = 2">
            <xsl:variable name="source2id" select="..." /> <!-- TODO: get second source id connected to output port of SplitBlock -->
            <Link>
              <xsl:attribute name="id">
                <xsl:value-of select="generate-id()" /> <!-- FIXME: this generates same id as $newid1 -->
              </xsl:attribute>
              <xsl:attribute name="source">
                <xsl:value-of select="$source2id" />
              </xsl:attribute>
              <xsl:attribute name="target">
                <xsl:value-of select="$newid1" />
              </xsl:attribute>
            </Link>
          </xsl:when>
          <xsl:otherwise>
            <Error>
              <xsl:attribute name="inputPorts">
                <xsl:value-of select="$inputPorts" />
              </xsl:attribute>
              <xsl:attribute name="outputPorts">
                <xsl:value-of select="$outputPorts" />
              </xsl:attribute>
            </Error>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:for-each>

      <xsl:for-each select="Link"> <!-- FIXME: don't copy links connected to SplitBlock -->
        <xsl:copy-of select="." />
      </xsl:for-each>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

2

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật