XSLT将任意递归结构的XML数据集转换为组织结构图

时间:2012-08-11 02:14:21

标签: xml xslt xpath xslt-1.0

我回答了我自己的问题,但我希望能够提供有关如何提高速度,清晰度或更惯用解决方案的建设性反馈。 :)

如果您想了解我问题的原始背景,请跳至 ~EDIT~ 或仔细阅读。

简而言之,我有一个类似于这样的XML数据集:

<org> <!-- Organization -->
  <dep> <!-- Department -->
    <nm>Department</nm> <!-- Department Name -->
    <emps> <!-- Head Department Employees -->
      <emp> <!-- One of the Head Employees -->
        <!-- Some Data, ie., L Name, F Name, and etc. (Omitted) -->
      </emp>
      <emp>
         <!-- Omitted -->
      </emp>
    </emps>
    <deps> <!-- So-called "Sub-Departments" -->
      <dep>
        <nm>Sub Department</nm>
        <emps>
          <emp>
            <!-- Omitted -->
          </emp>
        </emps>
      </dep>
    </deps>
  </dep>
</org>

这是一个非常小的样本,但关键的一点是部门可以任意数量的员工和任意数量的子部门。通常情况下,这样的数据集将是最重要的,顶层人员很少(领导者),并且越来越多地进入子部门。我一直试图做的事情 - 实际上是今天的半突破 - 正在弄清楚如何将XML数据集转换成一些简单的HTML组织结构图。

我最初想出的是在战略性地放置嵌套div&#34; float:left;&#34;并且&#34;明确:两者;&#34;样式,看起来像这样:

+-------------------------------------------------------+
|                         Dep                           |
| +------------------------+                            |
| | +--------+ +---------+ |                            |
| | |  Emp   | |   Emp   | |                            |
| | +--------+ +---------+ |                            |
| +------------------------+                            |
| +---------------------------------------------------+ |
| | +----------------------+ +----------------------+ | |
| | |         Dep          | |          Dep         | | |  
| | | +--------+ +-------+ | | +--------+ +-------+ | | |
| | | |  Emp   | |  Emp  | | | |  Emp   | |  Emp  | | | |
| | | +--------+ +-------+ | | +--------+ +-------+ | | |
| | +----------------------+ +----------------------+ | |
| +---------------------------------------------------+ |
+-------------------------------------------------------+

为我原始的ASCII艺术道歉。

我已经破坏了我的大脑试图想出一个可以产生这个的XSLT转换,而不是:

+-------------------------------------------------------+
|                         Dep                           |
|             +------------------------+                |
|             | +--------+ +---------+ |                |
|             | |  Emp   | |   Emp   | |                |
|             | +--------+ +---------+ |                |
|             +------------------------+                |
| +---------------------------------------------------+ |
| | +----------------------+ +----------------------+ | |
| | |         Dep          | |          Dep         | | |  
| | | +--------+ +-------+ | | +--------+ +-------+ | | |
| | | |  Emp   | |  Emp  | | | |  Emp   | | Emp   | | | |
| | | +--------+ +-------+ | | +--------+ +-------+ | | |
| | +----------------------+ +----------------------+ | |
| +---------------------------------------------------+ |
+-------------------------------------------------------+

使用浮动div可以做到这一点,虽然绝对定位有效,但唯一具有固定宽度的div是员工div,所以我需要以某种方式获取所有包含元素的总宽度,以正确调整每个部门,部门和员工div的大小。今天,我认为所有我需要做的就是计算每个部门div中所有最底层员工的数量,因为组织结构图往往会从上到下散开:< / p>

<xsl:template match="dep">
  <!-- Just assuming that dep's, deps's, and emps's have no margin, padding, or border
    for the sake of keeping this example simpler -->

  <xsl:variable name="emp_count"
    select="count(descendant::emp[parent::emps/parent::dep[count(child::deps)=0]])"/>

  <xsl:variable name="width" select="$emp_count * $emp_width"/>
  <xsl:variable name="mleft" select="$width div 2"/>

  <div style="position: absolute; left: 50%; width: {$width}px;
              margin-left: -{$mleft}px;">
    <xsl:apply-templates/>
  </div>
</xsl:template>

简而言之,虽然上面省略了很多,但除非我有任何拼写错误,否则它是有效的。但!这就是问题所在。这表明该组织遵循历史悠久的沉重传统。如何转换应输出类似内容的组织文档?

+---------------------------------------------------------------+
|                             Dep                               |
| +-----------------------------------------------------------+ |
| | +--------+ +---------+ +--------+ +---------+ +---------+ | |
| | |  Emp   | |   Emp   | |  Emp   | |   Emp   | |   Emp   | | |
| | +--------+ +---------+ +--------+ +---------+ +---------+ | |
| +-----------------------------------------------------------+ |
|     +---------------------------------------------------+     |
|     | +----------------------+ +----------------------+ |     |
|     | |         Dep          | |          Dep         | |     |  
|     | | +--------+ +-------+ | | +--------+ +-------+ | |     |
|     | | |  Emp   | |  Emp  | | | |  Emp   | | Emp   | | |     |
|     | | +--------+ +-------+ | | +--------+ +-------+ | |     |
|     | +----------------------+ +----------------------+ |     |
|     +---------------------------------------------------+     |
+---------------------------------------------------------------+
啊......没有什么能像破坏我最初认为会解决整个星期摧毁我大脑的问题的角落。事实上,这个角落的情况可能永远不会出现。事实上,也许不使用div,使用嵌套表可能更容易。

但是,现在我来了,我想知道......

考虑到这个角落案例,我们将如何计算顶级部门div的宽度,同时还要记住,这个角落案例可能会在层次结构中发生多次和任意深度?

〜EDIT〜

我想要转换这个XML源文档:

<?xml version="1.0" encoding="UTF-8"?>

<dep>
  <nm>Dep</nm>

  <emp>
    <nm>Emp</nm>
  </emp>

  <emp>
    <nm>Emp</nm>
  </emp>

  <emp>
    <nm>Emp</nm>
  </emp>

  <dep>
    <nm>Sub Dep 1</nm>

    <emp>
      <nm>Emp</nm>
    </emp>

    <emp>
      <nm>Emp</nm>
    </emp>

    <dep>
      <nm>Sub Dep 1-1</nm>

      <emp>
        <nm>Emp</nm>
      </emp>

      <emp>
        <nm>Emp</nm>
      </emp>

      <emp>
        <nm>Emp</nm>
      </emp>
    </dep>

    <dep>
      <nm>Sub Dep 1-2</nm>

      <emp>
        <nm>Emp</nm>
      </emp>

      <emp>
        <nm>Emp</nm>
      </emp>
    </dep>
  </dep>

  <dep>
    <nm>Sub Dep 2</nm>

    <emp>
      <nm>Emp</nm>
    </emp>

    <emp>
      <nm>Emp</nm>
    </emp>

    <emp>
      <nm>Emp</nm>
    </emp>

    <emp>
      <nm>Emp</nm>
    </emp>

    <dep>
      <nm>Sub Dep 2-1</nm>

      <emp>
        <nm>Emp</nm>
      </emp>
    </dep>

    <dep>
      <nm>Sub Dep 2-2</nm>

      <emp>
        <nm>Emp</nm>
      </emp>

      <emp>
        <nm>Emp</nm>
      </emp>
    </dep>
  </dep>

  <dep>
    <nm>Sub Dep 3</nm>

    <emp>
      <nm>Emp</nm>
    </emp>
  </dep>

  <dep>
    <nm>Sub Dep 4</nm>

    <emp>
      <nm>Emp</nm>
    </emp>

    <emp>
      <nm>Emp</nm>
    </emp>

    <dep>
      <nm>Sub Dep 4-1</nm>

      <emp>
        <nm>Emp</nm>
      </emp>

      <emp>
        <nm>Emp</nm>
      </emp>

      <emp>
        <nm>Emp</nm>
      </emp>

      <dep>
        <nm>Sub Dep 4-1-1</nm>

        <emp>
          <nm>Emp</nm>
        </emp>

        <emp>
          <nm>Emp</nm>
        </emp>

        <emp>
          <nm>Emp</nm>
        </emp>

        <emp>
          <nm>Emp</nm>
        </emp>

        <dep>
          <nm>Sub Dep 4-1-1-1</nm>

          <emp>
            <nm>Emp</nm>
          </emp>
        </dep>

        <dep>
          <nm>Sub Dep 4-1-1-2</nm>

          <emp>
            <nm>Emp</nm>
          </emp>
        </dep>
      </dep>
    </dep>

    <dep>
      <nm>Sub Dep 4-2</nm>

      <emp>
        <nm>Emp</nm>
      </emp>

      <emp>
        <nm>Emp</nm>
      </emp>
    </dep>
  </dep>
</dep>

...进入此XML结果文档:

<?xml version="1.0" encoding="UTF-8"?>

<deps emps-wide="16">
  <dep emps-wide="16">
    <nm>Dep</nm>

    <emps emps-wide="3">
      <emp>
        <nm>Emp</nm>
      </emp>

      <emp>
        <nm>Emp</nm>
      </emp>

      <emp>
        <nm>Emp</nm>
      </emp>
    </emps>

    <deps emps-wide="16">
      <dep emps-wide="5">
        <nm>Sub Dep 1</nm>

        <emps emps-wide="2">
          <emp>
            <nm>Emp</nm>
          </emp>

          <emp>
            <nm>Emp</nm>
          </emp>
        </emps>

        <deps emps-wide="5">
          <dep emps-wide="3">
            <nm>Sub Dep 1-1</nm>

            <emps emps-wide="3">
              <emp>
                <nm>Emp</nm>
              </emp>

              <emp>
                <nm>Emp</nm>
              </emp>

              <emp>
                <nm>Emp</nm>
              </emp>
            </emps>

            <deps emps-wide="0"/>
          </dep>

          <dep emps-wide="2">
            <nm>Sub Dep 1-2</nm>

            <emps emps-wide="2">
              <emp>
                <nm>Emp</nm>
              </emp>

              <emp>
                <nm>Emp</nm>
              </emp>
            </emps>

            <deps emps-wide="0"/>
          </dep>
        </deps>
      </dep>

      <dep emps-wide="4">
        <nm>Sub Dep 2</nm>

        <emps emps-wide="4">
          <emp>
            <nm>Emp</nm>
          </emp>

          <emp>
            <nm>Emp</nm>
          </emp>

          <emp>
            <nm>Emp</nm>
          </emp>

          <emp>
            <nm>Emp</nm>
          </emp>
        </emps>

        <deps emps-wide="3">
          <dep emps-wide="1">
            <nm>Sub Dep 2-1</nm>

            <emps emps-wide="1">
              <emp>
                <nm>Emp</nm>
              </emp>
            </emps>

            <deps emps-wide="0"/>
          </dep>

          <dep emps-wide="2">
            <nm>Sub Dep 2-2</nm>

            <emps emps-wide="2">
              <emp>
                <nm>Emp</nm>
              </emp>

              <emp>
                <nm>Emp</nm>
              </emp>
            </emps>

            <deps emps-wide="0"/>
          </dep>
        </deps>
      </dep>

      <dep emps-wide="1">
        <nm>Sub Dep 3</nm>

        <emps emps-wide="1">
          <emp>
            <nm>Emp</nm>
          </emp>
        </emps>

        <deps emps-wide="0"/>
      </dep>

      <dep emps-wide="6">
        <nm>Sub Dep 4</nm>

        <emps emps-wide="2">
          <emp>
            <nm>Emp</nm>
          </emp>

          <emp>
            <nm>Emp</nm>
          </emp>
        </emps>

        <deps emps-wide="6">
          <dep emps-wide="4">
            <nm>Sub Dep 4-1</nm>

            <emps emps-wide="3">
              <emp>
                <nm>Emp</nm>
              </emp>

              <emp>
                <nm>Emp</nm>
              </emp>

              <emp>
                <nm>Emp</nm>
              </emp>
            </emps>

            <deps emps-wide="4">
              <dep emps-wide="4">
                <nm>Sub Dep 4-1-1</nm>

                <emps emps-wide="4">
                  <emp>
                    <nm>Emp</nm>
                  </emp>

                  <emp>
                    <nm>Emp</nm>
                  </emp>

                  <emp>
                    <nm>Emp</nm>
                  </emp>

                  <emp>
                    <nm>Emp</nm>
                  </emp>
                </emps>

                <deps emps-wide="2">
                  <dep emps-wide="1">
                    <nm>Sub Dep 4-1-1-1</nm>

                    <emps emps-wide="1">
                      <emp>
                        <nm>Emp</nm>
                      </emp>
                    </emps>

                    <deps emps-wide="0"/>
                  </dep>

                  <dep emps-wide="1">
                    <nm>Sub Dep 4-1-1-2</nm>

                    <emps emps-wide="1">
                      <emp>
                        <nm>Emp</nm>
                      </emp>
                    </emps>

                    <deps emps-wide="0"/>
                  </dep>
                </deps>
              </dep>
            </deps>
          </dep>

          <dep emps-wide="2">
            <nm>Sub Dep 4-2</nm>

            <emps emps-wide="2">
              <emp>
                <nm>Emp</nm>
              </emp>

              <emp>
                <nm>Emp</nm>
              </emp>
            </emps>

            <deps emps-wide="0"/>
          </dep>
        </deps>
      </dep>
    </deps>
  </dep>
</deps>

为了完整起见,以下是用于生成该XML结果文档的大多数的XLST转换:

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="/">
    <xsl:call-template name="recurse-deps"/>
  </xsl:template>

  <xsl:template name="recurse-deps">
    <deps emps-wide="">
      <xsl:for-each select="dep">

          <dep emps-wide="">
            <nm><xsl:value-of select="nm"/></nm>

            <emps emps-wide="">
              <xsl:for-each select="emp">

                  <emp>
                    <nm><xsl:value-of select="nm"/></nm>
                  </emp>

              </xsl:for-each>
            </emps>

            <xsl:call-template name="recurse-deps"/>
          </dep>

      </xsl:for-each>
    </deps>
  </xsl:template>

</xsl:stylesheet>

请注意,我有一般化我的原始问题。我们甚至不关心如何显示作为组织结构图。这真的不是我想要在XSLT中学习解决的问题。

另请注意,我必须编辑XML结果文档,因为我 - 显然 - 不太清楚如何编写XSLT样式表来生成我想要的内容。否则,我不会在这里提问。 :)

以下是我在XSLT中尝试表达的难度,以及为什么我必须在运行该转换后手动编辑XML Result Document:

  1. 假设每个 emp 元素都是1个单位宽,无论该单位是什么。我们将其称为英寸,只是为了能够将其可视化。

  2. 每个 \ temps 元素都应该与其中的 \ temp 元素的数量一样宽。 (因此, emps 元素中包含三个 \ temp 元素,宽度为3英寸。)

  3. 每个 dep 元素都应该与 \ temps 元素或其 deps 元素一样宽,无论哪个元素最宽二。 (因此, dep 元素具有3英寸宽的 emps 元素和8英寸宽的 deps 元素将为8英寸宽。 dep 元素具有5英寸宽的 emps 元素和3英寸宽的 deps 元素,宽度为5英寸。)

  4. 每个 deps 元素应该与其所有 dep 元素的总和一样宽。宽度。 (所以,一个 deps 元素,一个3英寸宽的 dep ,一个5英寸宽的 dep ,另一个5英寸 - 宽 dep 然后宽13英寸。)

  5. dep (或部门)可以包含任意数量的 emp 元素(或员工)和任意数量的 dep 元素(或子部门),任意深入。

  6. 然后,我想在XSLT中解决的问题是如何将通用和递归结构化的XML源文档转换为XML结果文档,其中每个(或大多数)元素应该附加某种计算值对于那些依赖于其孩子的相同或类似计算值的人...依此类推......依此类推到任何可能是&#34;叶子节点&#34;。

    快速编辑:更多地考虑它,这类问题的一个类似例子就是改变一个&#34;文件系统&#34; XML源文档到XML结果文档,其中&#34;文件计数&#34;属性被添加到每个&#34;文件夹&#34;显示其中的文件总数及其&#34;子文件夹&#34;及其&#34;子文件夹&#34;等等。

    学习如何做到这一点的任何帮助 - 即使...... 如果有不止一种惯用的方式,那么最特别 - 将深表感谢。

1 个答案:

答案 0 :(得分:0)

我想出了一种做我一直想做的事情的方式。

此XSLT样式表将XML源文档转换为所需的XML结果文档(在 ~EDIT~ 之后在我的问题中找到)。

在我看来,可能有一个更好或更惯用的解决方案。我发现多次迭代XML源文档,将结果树片段从一个转换到下一个转换,并且每次迭代只进行简单的更改,这帮助我找到了这个解决方案。我很难想象,如果没有采用这种方法,这个XSLT样式表会有多复杂。我敢说我不可能一下子做到这一点。

即便如此,我确信还有改进的余地(速度和/或清晰度)。这是为了帮助其他任何可能处理类似于我的问题的人,或者是建设性的批评和改进,我同样会感激:

<?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:exsl  = "http://exslt.org/common"
                extension-element-prefixes = "exsl">

  <xsl:output method               = "xml"
              omit-xml-declaration = "no"
              indent               = "yes"
              encoding             = "UTF-8"/>

  <xsl:template match = "/">
    <xsl:variable name = "boxed-emps-and-deps">
      <xsl:call-template name = "box-emps-and-deps"/>
    </xsl:variable>

    <xsl:variable name = "added-emp-count-to-emps">
      <xsl:call-template name = "add-emp-count-to-emps">
        <xsl:with-param name = "last-pass" select = "$boxed-emps-and-deps"/>
      </xsl:call-template>
    </xsl:variable>

    <xsl:variable name = "added-emp-count-to-dep">
      <xsl:call-template name = "add-emp-count-to-dep">
        <xsl:with-param name = "last-pass" select = "$added-emp-count-to-emps"/>
      </xsl:call-template>
    </xsl:variable>

    <xsl:variable name = "added-emp-count-to-deps">
      <xsl:call-template name = "add-emp-count-to-deps">
        <xsl:with-param name = "last-pass" select = "$added-emp-count-to-dep"/>
      </xsl:call-template>
    </xsl:variable>

    <xsl:variable name = "recurred-dep-and-deps-emp-counts">
      <xsl:call-template name = "recur-dep-and-deps-emp-counts">
        <xsl:with-param name = "last-pass" select = "$added-emp-count-to-deps"/>
      </xsl:call-template>
    </xsl:variable>

    <xsl:copy-of select="exsl:node-set( $recurred-dep-and-deps-emp-counts )"/>
  </xsl:template>

  <xsl:template name = "box-emps-and-deps">
    <deps>
      <xsl:for-each select = "dep">
          <dep>
            <xsl:copy-of select = "nm"/>

            <emps>
              <xsl:for-each select = "emp">
                <xsl:copy-of select = "."/>
              </xsl:for-each>
            </emps>

            <xsl:call-template name = "box-emps-and-deps"/>
          </dep>
      </xsl:for-each>
    </deps>
  </xsl:template>

  <xsl:template name = "add-emp-count-to-emps">
    <xsl:param name = "last-pass"/>

    <xsl:for-each select = "exsl:node-set( $last-pass )/node()">
      <xsl:copy>
        <xsl:if test = "name( . ) = 'emps'">
          <xsl:attribute name = "emp-cnt">
            <xsl:value-of select = "count( emp )"/>
          </xsl:attribute>
        </xsl:if>

        <xsl:call-template name="add-emp-count-to-emps">
          <xsl:with-param name = "last-pass" select = "."/>
        </xsl:call-template>
      </xsl:copy>
    </xsl:for-each>
  </xsl:template>

  <xsl:template name = "add-emp-count-to-dep">
    <xsl:param name = "last-pass"/>

    <xsl:for-each select = "exsl:node-set( $last-pass )/node()|@*">
      <xsl:copy>
        <xsl:if test = "name( . ) = 'dep'">
          <xsl:attribute name = "emp-cnt">
            <xsl:value-of select = "emps/@emp-cnt"/>
          </xsl:attribute>
        </xsl:if>

        <xsl:call-template name="add-emp-count-to-dep">
          <xsl:with-param name = "last-pass" select = "."/>
        </xsl:call-template>
      </xsl:copy>
    </xsl:for-each>
  </xsl:template>

  <xsl:template name = "add-emp-count-to-deps">
    <xsl:param name = "last-pass"/>

    <xsl:for-each select = "exsl:node-set( $last-pass )/node()|@*">
      <xsl:copy>
        <xsl:if test = "name( . ) = 'deps'">
          <xsl:attribute name = "emp-cnt">
            <xsl:value-of select = "sum( dep/@emp-cnt )"/>
          </xsl:attribute>
        </xsl:if>

        <xsl:call-template name="add-emp-count-to-deps">
          <xsl:with-param name = "last-pass" select = "."/>
        </xsl:call-template>
      </xsl:copy>
    </xsl:for-each>
  </xsl:template>

  <xsl:template name = "recur-dep-and-deps-emp-counts">
    <xsl:param name = "last-pass"/>
    <xsl:variable name = "top-deps-emp-count" select="exsl:node-set( $last-pass )/deps/@emp-cnt"/>

    <xsl:variable name = "redid-dep-emp-count">
      <xsl:call-template name = "redo-dep-emp-count">
        <xsl:with-param name = "last-pass" select = "$last-pass"/>
      </xsl:call-template>
    </xsl:variable>

    <xsl:variable name = "redid-deps-emp-count">
      <xsl:call-template name = "redo-deps-emp-count">
        <xsl:with-param name = "last-pass" select = "$redid-dep-emp-count"/>
      </xsl:call-template>
    </xsl:variable>

    <xsl:variable name = "new-top-deps-emp-count" select = "exsl:node-set( $redid-deps-emp-count)/deps/@emp-cnt"/>

    <xsl:choose>
      <xsl:when test = "$top-deps-emp-count = $new-top-deps-emp-count">
        <xsl:copy-of select = "$redid-deps-emp-count"/>
      </xsl:when>

      <xsl:otherwise>
        <xsl:call-template name = "recur-dep-and-deps-emp-counts">
          <xsl:with-param name = "last-pass" select = "$redid-deps-emp-count"/>
        </xsl:call-template>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template name = "redo-dep-emp-count">
    <xsl:param name = "last-pass"/>

    <xsl:for-each select = "exsl:node-set( $last-pass )/node()|@*">
      <xsl:choose>
        <xsl:when test = "name( . ) = 'emp-cnt' and name( ./.. ) = 'dep'">
          <xsl:attribute name = "emp-cnt">
          <xsl:choose>
            <xsl:when test = "./../deps/@emp-cnt &gt; ./../emps/@emp-cnt">
              <xsl:value-of select = "./../deps/@emp-cnt"/>
            </xsl:when>

            <xsl:otherwise>
              <xsl:value-of select = "./../emps/@emp-cnt"/>
            </xsl:otherwise>
          </xsl:choose>
          </xsl:attribute>
        </xsl:when>

        <xsl:otherwise>
          <xsl:copy>
            <xsl:call-template name="redo-dep-emp-count">
              <xsl:with-param name = "last-pass" select = "."/>
            </xsl:call-template>
          </xsl:copy>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
  </xsl:template>

  <xsl:template name = "redo-deps-emp-count">
    <xsl:param name = "last-pass"/>

    <xsl:for-each select = "exsl:node-set( $last-pass )/node()|@*">
      <xsl:choose>
        <xsl:when test = "name( . ) = 'emp-cnt' and name( ./.. ) = 'deps'">
          <xsl:attribute name = "emp-cnt">
            <xsl:value-of select = "sum( ./../dep/@emp-cnt )"/>
          </xsl:attribute>
        </xsl:when>

        <xsl:otherwise>
          <xsl:copy>
            <xsl:call-template name="redo-deps-emp-count">
              <xsl:with-param name = "last-pass" select = "."/>
            </xsl:call-template>
          </xsl:copy>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>