将 Graphml 转换为 SVG 的 XSLT 样式表
XSLT stylesheet to convert Graphml to SVG
我在寻找将简单的 Graphml 文档转换为 SVG 图表的 XSLT 样式表时遇到了比预期更多的麻烦。我进行了相当广泛的搜索,但到目前为止我只取得了部分成功。这是我的输入 Graphml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<graphml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<graph id="G" edgedefault="directed">
<node id="d1e2"/>
<node id="d1e4"/>
<node id="d1e7"/>
<node id="d1e9"/>
<node id="d1e11"/>
<node id="d1e14"/>
<node id="d1e17"/>
<node id="d1e21"/>
<node id="d1e23"/>
<node id="d1e26"/>
<node id="d1e29"/>
<node id="d1e33"/>
<node id="d1e35"/>
<node id="d1e38"/>
<node id="d1e41"/>
<edge source="d1e2" target="d1e4"/>
<edge source="d1e2" target="d1e7"/>
<edge source="d1e7" target="d1e9"/>
<edge source="d1e9" target="d1e11"/>
<edge source="d1e9" target="d1e14"/>
<edge source="d1e9" target="d1e17"/>
<edge source="d1e7" target="d1e21"/>
<edge source="d1e21" target="d1e23"/>
<edge source="d1e21" target="d1e26"/>
<edge source="d1e21" target="d1e29"/>
<edge source="d1e7" target="d1e33"/>
<edge source="d1e33" target="d1e35"/>
<edge source="d1e33" target="d1e38"/>
<edge source="d1e33" target="d1e41"/>
</graph>
</graphml>
这是我的样式表(来自 http://www.svgopen.org/2003//papers/ComparisonXML2SVGTransformationMechanisms/index.html#S2)。请注意,我暂时禁用了边缘处理以专注于节点的放置:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/2000/svg">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<!-- for a 'graph' element, creates an 'svg' element -->
<xsl:template match="graph">
<!-- first give a CSS reference for the generated SVG -->
<xsl:processing-instruction name="xml-stylesheet">type="text/css" href="default.css"</xsl:processing-instruction>
<svg>
<!-- defs section for the arrow -->
<!-- ... -->
<!-- recurse 'node' elements of the graph to find graph root -->
<xsl:apply-templates select="node"/>
</svg>
</xsl:template>
<!-- recurse 'node' element to find graph root -->
<xsl:template match="node">
<!-- check if the first 'edge' has current 'node' as target -->
<xsl:apply-templates select="../edge[1]">
<xsl:with-param name="n" select="."/>
</xsl:apply-templates>
</xsl:template>
<!-- check if a 'node' ($n) is a target of the current 'edge' -->
<xsl:template match="edge">
<xsl:param name="n">null</xsl:param>
<!-- if the 'node' is not a target of the current 'edge' -->
<xsl:if test="not(@target=$n/@id)">
<!-- advance to the next edge -->
<xsl:apply-templates select="following-sibling::edge[position()=1]">
<xsl:with-param name="n" select="$n"/>
</xsl:apply-templates>
<!-- if all edges have been queried -->
<xsl:if test="not(following-sibling::edge[position()=1])">
<!-- the 'node' ($n) is the root, create it -->
<xsl:call-template name="create-node">
<xsl:with-param name="n" select="$n"/>
</xsl:call-template>
</xsl:if>
</xsl:if>
</xsl:template>
<!-- transform a 'node' to SVG and recurse through its children -->
<xsl:template name="create-node">
<xsl:param name="n">null</xsl:param>
<xsl:param name="level">0</xsl:param>
<xsl:param name="count">0</xsl:param>
<xsl:param name="edge">null</xsl:param>
<xsl:param name="x1">0</xsl:param>
<xsl:param name="y1">0</xsl:param>
<!-- some helpers -->
<xsl:variable name="side" select="1-2*($count mod 2)"/>
<xsl:variable name="x" select="$level*150"/>
<xsl:variable name="y" select="$y1 - 50+$side*ceiling($count div 2)*150"/>
<!-- create the 'node' itself and position it -->
<g class="node">
<rect x="{$x}" y="{$y}" width="100" height="100"/>
<text text-anchor="middle" x="{$x+50}" y="{$y+55}">
<xsl:value-of select="$n/@id"/>
</text>
</g>
<!-- if there is an 'edge' ($edge) draw it -->
<xsl:if test="$edge!='null'">
<!-- the 'edge' position goes from previous 'node' position to $n one -->
<!--
<line class="edge" x1="{$x1}" y1="{$y1}" x2="{$x}" y2="{$y+50}">
<xsl:attribute name="style">marker-end:url(#arrow)</xsl:attribute>
</line>
-->
</xsl:if>
<!-- now that the 'node' is created, recurse to children through edges -->
<xsl:call-template name="query-edge">
<xsl:with-param name="edge" select="$n/../edge[@source=$n/@id][1]"/>
<xsl:with-param name="x1" select="$x+100"/>
<xsl:with-param name="y1" select="$y+50"/>
<xsl:with-param name="n" select="$n"/>
<!-- going to the upper level, increment level -->
<xsl:with-param name="level" select="$level+1"/>
<!-- going to the first child, set counter to 0 -->
<xsl:with-param name="count" select="0"/>
</xsl:call-template>
</xsl:template>
<!-- recurse a 'node' ($n) edges to find 'node' children -->
<xsl:template name="query-edge">
<xsl:param name="edge">null</xsl:param>
<xsl:param name="x1">0</xsl:param>
<xsl:param name="y1">0</xsl:param>
<xsl:param name="n">null</xsl:param>
<xsl:param name="level">0</xsl:param>
<xsl:param name="count">0</xsl:param>
<xsl:variable name="target" select="$edge/@target"/>
<!-- if there is an 'edge' -->
<xsl:if test="$edge!='null'">
<!-- go down the tree, create the 'node' of the 'edge' target -->
<xsl:call-template name="create-node">
<xsl:with-param name="n" select="$edge/../node[@id=$target]"/>
<xsl:with-param name="level" select="$level"/>
<xsl:with-param name="count" select="$count"/>
<xsl:with-param name="edge" select="$edge"/>
<xsl:with-param name="x1" select="$x1"/>
<xsl:with-param name="y1" select="$y1"/>
</xsl:call-template>
<!-- go to the next 'edge' that has also the 'node' ($n) has source -->
<xsl:variable name="next-edge" select="$edge/following-sibling::edge[position()=1][@source=$n/@id]"/>
<xsl:call-template name="query-edge">
<xsl:with-param name="edge" select="$next-edge"/>
<xsl:with-param name="x1" select="$x1"/>
<xsl:with-param name="y1" select="$y1"/>
<xsl:with-param name="n" select="$n"/>
<xsl:with-param name="level" select="$level"/>
<!-- next 'edge', increment counter -->
<xsl:with-param name="count" select="$count+1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
当我通过 Saxon 运行 时,我得到以下 SVG 文件:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/css" href="default.css"?><svg xmlns="http://www.w3.org/2000/svg">
<g class="node">
<rect x="0" y="-50" width="100" height="100"/>
<text text-anchor="middle" x="50" y="5">d1e2</text>
</g>
<g class="node">
<rect x="150" y="-50" width="100" height="100"/>
<text text-anchor="middle" x="200" y="5">d1e4</text>
</g>
<g class="node">
<rect x="150" y="-200" width="100" height="100"/>
<text text-anchor="middle" x="200" y="-145">d1e7</text>
</g>
<g class="node">
<rect x="300" y="-200" width="100" height="100"/>
<text text-anchor="middle" x="350" y="-145">d1e9</text>
</g>
<g class="node">
<rect x="450" y="-200" width="100" height="100"/>
<text text-anchor="middle" x="500" y="-145">d1e11</text>
</g>
<g class="node">
<rect x="450" y="-350" width="100" height="100"/>
<text text-anchor="middle" x="500" y="-295">d1e14</text>
</g>
<g class="node">
<rect x="450" y="-50" width="100" height="100"/>
<text text-anchor="middle" x="500" y="5">d1e17</text>
</g>
</svg>
这很好,但是它丢失了 d1e17 节点之后的所有节点,我不明白为什么。
谁能找出样式表中的错误?或者有没有人有更好的样式表用于此目的?
感谢您的帮助,马丁
如果先将平面节点列表转换为树,则可以使用轴查找有关结构的信息。
尝试这样的事情:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="2.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:template match="/graphml/graph">
<!-- Find the root ID -->
<xsl:variable name="rootId">
<xsl:for-each select="node">
<xsl:variable name="nodeId" select="@id"/>
<xsl:if test="not(../edge[@target=$nodeId])">
<xsl:value-of select="$nodeId"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<!-- Turn flat list into a tree -->
<xsl:variable name="tree">
<xsl:apply-templates select="node[@id=$rootId]"/>
</xsl:variable>
<!-- Turn tree into a flat list of svg elements -->
<svg>
<g transform="translate(50 50)">
<xsl:apply-templates select="$tree"/>
</g>
</svg>
</xsl:template>
<xsl:template match="node">
<xsl:variable name="nodeId" select="@id"/>
<xsl:variable name="childIds" select="//edge[@source=$nodeId]/@target"/>
<treeNode id="{@id}">
<xsl:apply-templates select="//node[@id=$childIds]"/>
</treeNode>
</xsl:template>
<xsl:template match="svg:treeNode">
<xsl:variable name="level" select="count(ancestor::*)"/>
<xsl:variable name="leafChildren" select="count(descendant::*[not(descendant::*)])"/>
<xsl:variable name="earlierChildren" select="count(preceding::*[not(descendant::*)])"/>
<xsl:variable name="x" select="100 * $level"/>
<xsl:variable name="y" select="50 * $earlierChildren + 25 * max((0, $leafChildren - 1))"/>
<g class="node">
<circle cx="{$x}" cy="{$y}" r="10"/>
<text x="{$x - 10}" y="{$y + 25}"><xsl:value-of select="@id"/></text>
<!-- Draw line to parent -->
<xsl:if test="$level != 0">
<xsl:variable name="parentLevel" select="$level - 1"/>
<xsl:variable name="parentLeafChildren" select="count(../descendant::*[not(descendant::*)])"/>
<xsl:variable name="parentEarlierChildren" select="count(../preceding::*[not(descendant::*)])"/>
<xsl:variable name="parentX" select="100 * $parentLevel"/>
<xsl:variable name="parentY" select="50 * $parentEarlierChildren + 25 * max((0, $parentLeafChildren - 1))"/>
<path d="M {$parentX} {$parentY} C {$parentX + 50} {$parentY}, {$x - 50} {$y}, {$x} {$y}" stroke="black" fill="transparent" stroke-width="2"/>
</xsl:if>
</g>
<xsl:apply-templates select="child::*"/>
</xsl:template>
</xsl:stylesheet>
结果是这样的:
我在寻找将简单的 Graphml 文档转换为 SVG 图表的 XSLT 样式表时遇到了比预期更多的麻烦。我进行了相当广泛的搜索,但到目前为止我只取得了部分成功。这是我的输入 Graphml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<graphml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<graph id="G" edgedefault="directed">
<node id="d1e2"/>
<node id="d1e4"/>
<node id="d1e7"/>
<node id="d1e9"/>
<node id="d1e11"/>
<node id="d1e14"/>
<node id="d1e17"/>
<node id="d1e21"/>
<node id="d1e23"/>
<node id="d1e26"/>
<node id="d1e29"/>
<node id="d1e33"/>
<node id="d1e35"/>
<node id="d1e38"/>
<node id="d1e41"/>
<edge source="d1e2" target="d1e4"/>
<edge source="d1e2" target="d1e7"/>
<edge source="d1e7" target="d1e9"/>
<edge source="d1e9" target="d1e11"/>
<edge source="d1e9" target="d1e14"/>
<edge source="d1e9" target="d1e17"/>
<edge source="d1e7" target="d1e21"/>
<edge source="d1e21" target="d1e23"/>
<edge source="d1e21" target="d1e26"/>
<edge source="d1e21" target="d1e29"/>
<edge source="d1e7" target="d1e33"/>
<edge source="d1e33" target="d1e35"/>
<edge source="d1e33" target="d1e38"/>
<edge source="d1e33" target="d1e41"/>
</graph>
</graphml>
这是我的样式表(来自 http://www.svgopen.org/2003//papers/ComparisonXML2SVGTransformationMechanisms/index.html#S2)。请注意,我暂时禁用了边缘处理以专注于节点的放置:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/2000/svg">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<!-- for a 'graph' element, creates an 'svg' element -->
<xsl:template match="graph">
<!-- first give a CSS reference for the generated SVG -->
<xsl:processing-instruction name="xml-stylesheet">type="text/css" href="default.css"</xsl:processing-instruction>
<svg>
<!-- defs section for the arrow -->
<!-- ... -->
<!-- recurse 'node' elements of the graph to find graph root -->
<xsl:apply-templates select="node"/>
</svg>
</xsl:template>
<!-- recurse 'node' element to find graph root -->
<xsl:template match="node">
<!-- check if the first 'edge' has current 'node' as target -->
<xsl:apply-templates select="../edge[1]">
<xsl:with-param name="n" select="."/>
</xsl:apply-templates>
</xsl:template>
<!-- check if a 'node' ($n) is a target of the current 'edge' -->
<xsl:template match="edge">
<xsl:param name="n">null</xsl:param>
<!-- if the 'node' is not a target of the current 'edge' -->
<xsl:if test="not(@target=$n/@id)">
<!-- advance to the next edge -->
<xsl:apply-templates select="following-sibling::edge[position()=1]">
<xsl:with-param name="n" select="$n"/>
</xsl:apply-templates>
<!-- if all edges have been queried -->
<xsl:if test="not(following-sibling::edge[position()=1])">
<!-- the 'node' ($n) is the root, create it -->
<xsl:call-template name="create-node">
<xsl:with-param name="n" select="$n"/>
</xsl:call-template>
</xsl:if>
</xsl:if>
</xsl:template>
<!-- transform a 'node' to SVG and recurse through its children -->
<xsl:template name="create-node">
<xsl:param name="n">null</xsl:param>
<xsl:param name="level">0</xsl:param>
<xsl:param name="count">0</xsl:param>
<xsl:param name="edge">null</xsl:param>
<xsl:param name="x1">0</xsl:param>
<xsl:param name="y1">0</xsl:param>
<!-- some helpers -->
<xsl:variable name="side" select="1-2*($count mod 2)"/>
<xsl:variable name="x" select="$level*150"/>
<xsl:variable name="y" select="$y1 - 50+$side*ceiling($count div 2)*150"/>
<!-- create the 'node' itself and position it -->
<g class="node">
<rect x="{$x}" y="{$y}" width="100" height="100"/>
<text text-anchor="middle" x="{$x+50}" y="{$y+55}">
<xsl:value-of select="$n/@id"/>
</text>
</g>
<!-- if there is an 'edge' ($edge) draw it -->
<xsl:if test="$edge!='null'">
<!-- the 'edge' position goes from previous 'node' position to $n one -->
<!--
<line class="edge" x1="{$x1}" y1="{$y1}" x2="{$x}" y2="{$y+50}">
<xsl:attribute name="style">marker-end:url(#arrow)</xsl:attribute>
</line>
-->
</xsl:if>
<!-- now that the 'node' is created, recurse to children through edges -->
<xsl:call-template name="query-edge">
<xsl:with-param name="edge" select="$n/../edge[@source=$n/@id][1]"/>
<xsl:with-param name="x1" select="$x+100"/>
<xsl:with-param name="y1" select="$y+50"/>
<xsl:with-param name="n" select="$n"/>
<!-- going to the upper level, increment level -->
<xsl:with-param name="level" select="$level+1"/>
<!-- going to the first child, set counter to 0 -->
<xsl:with-param name="count" select="0"/>
</xsl:call-template>
</xsl:template>
<!-- recurse a 'node' ($n) edges to find 'node' children -->
<xsl:template name="query-edge">
<xsl:param name="edge">null</xsl:param>
<xsl:param name="x1">0</xsl:param>
<xsl:param name="y1">0</xsl:param>
<xsl:param name="n">null</xsl:param>
<xsl:param name="level">0</xsl:param>
<xsl:param name="count">0</xsl:param>
<xsl:variable name="target" select="$edge/@target"/>
<!-- if there is an 'edge' -->
<xsl:if test="$edge!='null'">
<!-- go down the tree, create the 'node' of the 'edge' target -->
<xsl:call-template name="create-node">
<xsl:with-param name="n" select="$edge/../node[@id=$target]"/>
<xsl:with-param name="level" select="$level"/>
<xsl:with-param name="count" select="$count"/>
<xsl:with-param name="edge" select="$edge"/>
<xsl:with-param name="x1" select="$x1"/>
<xsl:with-param name="y1" select="$y1"/>
</xsl:call-template>
<!-- go to the next 'edge' that has also the 'node' ($n) has source -->
<xsl:variable name="next-edge" select="$edge/following-sibling::edge[position()=1][@source=$n/@id]"/>
<xsl:call-template name="query-edge">
<xsl:with-param name="edge" select="$next-edge"/>
<xsl:with-param name="x1" select="$x1"/>
<xsl:with-param name="y1" select="$y1"/>
<xsl:with-param name="n" select="$n"/>
<xsl:with-param name="level" select="$level"/>
<!-- next 'edge', increment counter -->
<xsl:with-param name="count" select="$count+1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
当我通过 Saxon 运行 时,我得到以下 SVG 文件:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/css" href="default.css"?><svg xmlns="http://www.w3.org/2000/svg">
<g class="node">
<rect x="0" y="-50" width="100" height="100"/>
<text text-anchor="middle" x="50" y="5">d1e2</text>
</g>
<g class="node">
<rect x="150" y="-50" width="100" height="100"/>
<text text-anchor="middle" x="200" y="5">d1e4</text>
</g>
<g class="node">
<rect x="150" y="-200" width="100" height="100"/>
<text text-anchor="middle" x="200" y="-145">d1e7</text>
</g>
<g class="node">
<rect x="300" y="-200" width="100" height="100"/>
<text text-anchor="middle" x="350" y="-145">d1e9</text>
</g>
<g class="node">
<rect x="450" y="-200" width="100" height="100"/>
<text text-anchor="middle" x="500" y="-145">d1e11</text>
</g>
<g class="node">
<rect x="450" y="-350" width="100" height="100"/>
<text text-anchor="middle" x="500" y="-295">d1e14</text>
</g>
<g class="node">
<rect x="450" y="-50" width="100" height="100"/>
<text text-anchor="middle" x="500" y="5">d1e17</text>
</g>
</svg>
这很好,但是它丢失了 d1e17 节点之后的所有节点,我不明白为什么。
谁能找出样式表中的错误?或者有没有人有更好的样式表用于此目的?
感谢您的帮助,马丁
如果先将平面节点列表转换为树,则可以使用轴查找有关结构的信息。
尝试这样的事情:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="2.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:template match="/graphml/graph">
<!-- Find the root ID -->
<xsl:variable name="rootId">
<xsl:for-each select="node">
<xsl:variable name="nodeId" select="@id"/>
<xsl:if test="not(../edge[@target=$nodeId])">
<xsl:value-of select="$nodeId"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<!-- Turn flat list into a tree -->
<xsl:variable name="tree">
<xsl:apply-templates select="node[@id=$rootId]"/>
</xsl:variable>
<!-- Turn tree into a flat list of svg elements -->
<svg>
<g transform="translate(50 50)">
<xsl:apply-templates select="$tree"/>
</g>
</svg>
</xsl:template>
<xsl:template match="node">
<xsl:variable name="nodeId" select="@id"/>
<xsl:variable name="childIds" select="//edge[@source=$nodeId]/@target"/>
<treeNode id="{@id}">
<xsl:apply-templates select="//node[@id=$childIds]"/>
</treeNode>
</xsl:template>
<xsl:template match="svg:treeNode">
<xsl:variable name="level" select="count(ancestor::*)"/>
<xsl:variable name="leafChildren" select="count(descendant::*[not(descendant::*)])"/>
<xsl:variable name="earlierChildren" select="count(preceding::*[not(descendant::*)])"/>
<xsl:variable name="x" select="100 * $level"/>
<xsl:variable name="y" select="50 * $earlierChildren + 25 * max((0, $leafChildren - 1))"/>
<g class="node">
<circle cx="{$x}" cy="{$y}" r="10"/>
<text x="{$x - 10}" y="{$y + 25}"><xsl:value-of select="@id"/></text>
<!-- Draw line to parent -->
<xsl:if test="$level != 0">
<xsl:variable name="parentLevel" select="$level - 1"/>
<xsl:variable name="parentLeafChildren" select="count(../descendant::*[not(descendant::*)])"/>
<xsl:variable name="parentEarlierChildren" select="count(../preceding::*[not(descendant::*)])"/>
<xsl:variable name="parentX" select="100 * $parentLevel"/>
<xsl:variable name="parentY" select="50 * $parentEarlierChildren + 25 * max((0, $parentLeafChildren - 1))"/>
<path d="M {$parentX} {$parentY} C {$parentX + 50} {$parentY}, {$x - 50} {$y}, {$x} {$y}" stroke="black" fill="transparent" stroke-width="2"/>
</xsl:if>
</g>
<xsl:apply-templates select="child::*"/>
</xsl:template>
</xsl:stylesheet>
结果是这样的: