将 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>

结果是这样的: