1

我回答了我自己的问题,但希望对我如何改进它以提高速度、清晰度或更惯用的解决方案提出建设性的反馈。:)

如果您想了解我的问题的原始上下文,请跳至〜编辑〜或通读它。

简而言之,我有一个如下所示的 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 组织结构图。

我最初想出的是用战略性放置的“float:left”吐出嵌套的div;和“明确:两者;” 样式,看起来像这样:

+-------------------------------------------------------+
|                         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 中所有最底层员工 div 的数量,因为组织结构图往往从上到下呈扇形分布:

<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 的宽度,同时记住这种极端情况可能不止一次出现并且在层次结构中任意深处?

~编辑~

我想打开这个 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 结果文档:

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

  2. 每个emps元素的宽度应与其中emp元素的数量一样宽。(因此,其中包含三个emp元素的emps元素将是 3 英寸宽。)

  3. 每个dep元素都应与其emps元素或它的deps元素一样宽,以两者中最宽者为准。(因此,具有 3 英寸宽emps元素和 8 英寸宽deps元素的dep元素将是 8 英寸宽。具有 5 英寸宽emps元素和 3 英寸宽deps的dep元素元素将是 5 英寸宽。)

  4. 每个deps元素的宽度应与其所有dep元素的宽度之和一样宽。(因此,具有 3 英寸宽的dep、 5 英寸宽的dep和另一个 5 英寸宽的dep的deps元素将是 13 英寸宽。)

  5. 一个部门(或部门)可以有任意数量的emp元素(或雇员)和任意数量的dep元素(或子部门),深度任意

那么,我想在 XSLT 中解决的问题是如何将通用且递归结构化的 XML 源文档转换为 XML 结果文档,其中每个(或大多数)元素都应该附加某种计算值,即取决于其子节点的相同或相似的计算值......等等......等等......一直到可能是“叶节点”。

快速编辑:多想一下,此类问题的一个类似示例是将“文件系统”XML 源文档转换为 XML 结果文档,其中将“文件计数”属性添加到每个“文件夹”中,显示其中的文件总数及其“子文件夹”,以及它们的“子文件夹”等等。

任何帮助学习如何做到这一点 - 即使......不。尤其是如果有不止一种惯用的方式来做到这一点 - 将不胜感激。

4

1 回答 1

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>
于 2012-08-13T04:40:35.860 回答