XSLT: объединить набор древовидных иерархий - PullRequest
2 голосов
/ 31 марта 2009

У меня есть XML-документ, основанный на том, что Excel создает при сохранении в виде «XML Spreadsheet 2003 (* .xml)».

Сама электронная таблица содержит раздел заголовка с иерархией меток:

 | A     B     C     D     E     F     G     H     I
-+-----------------------------------------------------
1| a1                                  a2
2| a11         a12         a13         a21   a22
3| a111  a112  a121  a122  a131  a132        a221  a222

Эта иерархия присутствует на всех листах рабочей книги и везде выглядит более или менее одинаково.

Excel XML работает точно так же, как обычные таблицы HTML. (<row> с, которые содержат <cell> с). Я был в состоянии преобразовать все в такую ​​древовидную структуру:

<node title="a1" col="1">
  <node title="a11" col="1">
    <node title="a111" col="1"/>
    <node title="a112" col="2"/>
  </node>
  <node title="a12" col="3">
    <node title="a121" col="3" />
    <node title="a122" col="4" />
  </node>
  <!-- and so on -->
</node>

Но вот сложность:

  • существует более одного рабочего листа, поэтому для каждого из них есть дерево
  • иерархия может немного отличаться на каждом листе, деревья не будут равны (например, лист 2 может иметь "a113", в то время как другие не имеют)
  • Глубина дерева явно не ограничена
  • однако метки должны быть одинаковыми на всех листах, что означает, что они могут использоваться для группировки

Я бы хотел объединить эти отдельные деревья в одно, которое выглядит так:

<node title="a1">
  <col on="sheet1">1</col>
  <col on="sheet2">1</col>
  <node title="a11">
    <col on="sheet1">1</col>
    <col on="sheet2">1</col>
    <node title="a111">
      <col on="sheet1">1</col>
      <col on="sheet2">1</col>
    </node>
    <node title="a112">
      <col on="sheet1">2</col>
      <col on="sheet2">2</col>
    </node>
    <node title="a113"><!-- different here -->
      <col on="sheet2">3</col>
    </node>
  </node>
  <node title="a12">
    <col on="sheet1">3</col>
    <col on="sheet2">4</col>
    <node title="a121">
      <col on="sheet1">3</col>
      <col on="sheet2">4</col>
    </node>
    <node title="a122">
      <col on="sheet1">4</col>
      <col on="sheet2">5</col>
    </node>
  </node>
  <!-- and so on -->
</node>

В идеале я хотел бы иметь возможность выполнить слияние до Я даже построил три структуры из Excel XML (если вы меня начнете, это будет замечательно ). Но поскольку я понятия не имею, как мне это сделать, слияние после того, как деревья будут построены (т.е. описанная выше ситуация), будет в порядке.

Спасибо за ваше время. :)

Ответы [ 2 ]

2 голосов
/ 01 апреля 2009

Вот одно из возможных решений в XSLT 1.0 :

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

    <xsl:template match="/*">
      <t>
        <xsl:apply-templates
           select="node[@title='a1'][1]">
          <xsl:with-param name="pOther"
            select="node[@title='a1'][2]"/>
        </xsl:apply-templates>
      </t>
    </xsl:template>

    <xsl:template match="node">
      <xsl:param name="pOther"/>

      <node title="{@title}">
        <col on="sheet1">
          <xsl:value-of select="@col"/>
        </col>
          <xsl:choose>
            <xsl:when test="not($pOther)">
              <xsl:apply-templates mode="copy">
                <xsl:with-param name="pSheet" select="'sheet1'"/>
              </xsl:apply-templates>
            </xsl:when>
            <xsl:otherwise>
              <col on="sheet2">
                <xsl:value-of select="$pOther/@col"/>
              </col>
              <xsl:for-each select=
                "node[@title = $pOther/node/@title]">

                <xsl:apply-templates select=".">
                  <xsl:with-param name="pOther" select=
                   "$pOther/node[@title = current()/@title]"/>
                </xsl:apply-templates>
              </xsl:for-each>

              <xsl:apply-templates mode="copy" select=
                "node[not(@title = $pOther/node/@title)]">
                <xsl:with-param name="pSheet" select="'sheet1'"/>
              </xsl:apply-templates>

              <xsl:apply-templates mode="copy" select=
                "$pOther/node[not(@title = current()/node/@title)]">
                <xsl:with-param name="pSheet" select="'sheet2'"/>
              </xsl:apply-templates>
            </xsl:otherwise>
          </xsl:choose>
      </node>
    </xsl:template>

    <xsl:template match="node" mode="copy">
      <xsl:param name="pSheet"/>

      <node title="{@title}">
        <col on="{$pSheet}">
          <xsl:value-of select="@col"/>
        </col>

        <xsl:apply-templates select="node" mode="copy">
          <xsl:with-param name="pSheet" select="$pSheet"/>
        </xsl:apply-templates>
      </node>
    </xsl:template>
</xsl:stylesheet>

Когда вышеуказанное преобразование применяется к этому XML-документу (объединение двух XML-документов под общим верхним узлом - оставлено в качестве упражнения для читателя :)):

<t>
    <node title="a1" col="1">
        <node title="a11" col="1">
            <node title="a111" col="1"/>
            <node title="a112" col="2"/>
        </node>
        <node title="a12" col="3">
            <node title="a121" col="3" />
            <node title="a122" col="4" />
        </node>
        <!-- and so on -->
    </node>
    <node title="a1" col="1">
        <node title="a11" col="1">
            <node title="a111" col="1"/>
            <node title="a112" col="2"/>
            <node title="a113" col="3"/>
        </node>
        <node title="a12" col="4">
            <node title="a121" col="4" />
            <node title="a122" col="5" />
        </node>
        <!-- and so on -->
    </node>
</t>

Требуемый результат получен:

<t>
    <node title="a1">
        <col on="sheet1">1</col>
        <col on="sheet2">1</col>
        <node title="a11">
            <col on="sheet1">1</col>
            <col on="sheet2">1</col>
            <node title="a111">
                <col on="sheet1">1</col>
                <col on="sheet2">1</col>
            </node>
            <node title="a112">
                <col on="sheet1">2</col>
                <col on="sheet2">2</col>
            </node>
            <node title="a113">
                <col on="sheet2">3</col>
            </node>
        </node>
        <node title="a12">
            <col on="sheet1">3</col>
            <col on="sheet2">4</col>
            <node title="a121">
                <col on="sheet1">3</col>
                <col on="sheet2">4</col>
            </node>
            <node title="a122">
                <col on="sheet1">4</col>
                <col on="sheet2">5</col>
            </node>
        </node>
    </node>
</t>

Обратите внимание на следующее:

  1. Мы предполагаем, что оба верхних элемента node имеют "a1" в качестве значения их атрибута title. Это можно легко обобщить.

  2. Шаблон, соответствующий node, имеет параметр с именем pOther, который является соответствующим элементом с именем node из другого документа. Этот шаблон применяется - только если существует $ pOther.

  3. Когда не существует соответствующего элемента с именем node, применяется другой шаблон, также соответствующий node, но в режиме copy. Этот шаблон имеет параметр с именем pSheet, значением которого является имя листа (строка), к которой принадлежит этот элемент.

1 голос
/ 31 марта 2009

Как насчет вызываемого шаблона, принимающего номер листа в качестве параметра, который проверяет ввод и возвращает правильный узел "col", если он появляется в XML этого листа, и ничего, если он не появляется. На каждом узле вызывайте его один раз для каждого листа.

Чтобы объединить деревья, может быть шаблон, который ищет всех дочерних элементов текущего узла на любом листе и рекурсивно сам для себя для каждого из них.

Извините, нет примера кода, я считаю, что написание XSLT идет довольно медленно, возможно потому, что я делаю это не часто. Так что я вполне мог пропустить что-то решающее. Но сложив все это, вы получите что-то вроде:

  • получить заголовок "/ узел". С таким названием:
    • искать во всех листах по этому заголовку, испуская узел "col" для каждого
    • поиск во всех листах дочерних узлов с этим заголовком (исключая дубликаты)
    • Рекурсировать на каждом из этих названий.

Вот несколько фрагментов для удаления дубликатов различными способами:

http://www.dpawson.co.uk/xsl/sect2/N2696.html

Чтение нескольких документов зависит от процессора, но если все остальное не сработает, то, скорее всего, с этим справится любой старый сценарий, если вы знаете, что все они будут иметь одинаковую кодировку, не используйте конфликтующие идентификаторы и т. д.

...