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