XSLT - Группировка элементов после значения индекса - PullRequest
1 голос
/ 03 мая 2019

Я пишу XSLT, который должен выводить определенные элементы данных, но в контейнеры фиксированной длины. Хотя обычно исходные данные содержат меньше элементов, чем фиксированная длина, в исходных данных может содержаться больше элементов, чем фиксированная длина. Когда происходит последнее, мне нужно вывести элементы по отдельности в определенный индекс, а затем сгруппировать оставшихся братьев и сестер на основе свойства, чтобы я мог использовать объединение и суммирование по группам. Я могу добиться всего этого, за исключением группировки братьев и сестер после заданного значения индекса.

Ниже приведена упрощенная версия того, чего я пытаюсь достичь. Предположим, что фиксированная длина контейнера равна 5, и единственными Type значениями, которые должны быть выведены, являются 1 и 2, опуская 3.

Пример ввода XML

<?xml version="1.0" encoding="utf-8"?>
<Company>
  <Locations>
    <Location>
      <Id>1</Id>
      <Shoppers>
        <Shopper>
          <Products>
            <Product>
              <Amount>5.00</Amount>
              <Id>1</Id>
              <Name>A</Name>
              <Type>1</Type>
            </Product>
            <Product>
              <Amount>10.00</Amount>
              <Id>1</Id>
              <Name>B</Name>
              <Type>2</Type>
            </Product>
            <Product>
              <Amount>15.00</Amount>
              <Id>1</Id>
              <Name>C</Name>
              <Type>3</Type>
            </Product>
            <Product>
              <Amount>20.00</Amount>
              <Id>1</Id>
              <Name>D</Name>
              <Type>1</Type>
            </Product>
          </Products>
        </Shopper>
        <Shopper>
          <Products>
            <Product>
              <Amount>25.00</Amount>
              <Id>1</Id>
              <Name>E</Name>
              <Type>2</Type>
            </Product>
            <Product>
              <Amount>30.00</Amount>
              <Id>1</Id>
              <Name>F</Name>
              <Type>1</Type>
            </Product>
            <Product>
              <Amount>35.00</Amount>
              <Id>1</Id>
              <Name>G</Name>
              <Type>2</Type>
            </Product>
            <Product>
              <Amount>40.00</Amount>
              <Id>1</Id>
              <Name>H</Name>
              <Type>1</Type>
            </Product>
          </Products>
        </Shopper>
      </Shoppers>
    </Location>
    <Location>
      <Id>2</Id>
      <Shoppers>
        <Shopper>
          <Products>
            <Product>
              <Amount>5.00</Amount>
              <Id>2</Id>
              <Name>I</Name>
              <Type>A</Type>
            </Product>
            <Product>
              <Amount>10.00</Amount>
              <Id>2</Id>
              <Name>J</Name>
              <Type>B</Type>
            </Product>
          </Products>
        </Shopper>
        <Shopper>
          <Products>
            <Product>
              <Amount>25.00</Amount>
              <Id>2</Id>
              <Name>K</Name>
              <Type>A</Type>
            </Product>
            <Product>
              <Amount>30.00</Amount>
              <Id>2</Id>
              <Name>L</Name>
              <Type>B</Type>
            </Product>
            <Product>
              <Amount>35.00</Amount>
              <Id>2</Id>
              <Name>M</Name>
              <Type>B</Type>
            </Product>
          </Products>
        </Shopper>
      </Shoppers>
    </Location>
  </Locations>
</Company>

XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl customCode"  xmlns:customCode="urn:customCode">
  <xsl:output method="xml" indent="yes" encoding="utf-8"/>

  <xsl:key name="product-distinct" match="Product" use="concat(Id,Type)"/>

  <xsl:template match="/">
    <Locations>
      <xsl:for-each select="Company/Locations/Location">
        <xsl:variable name="id" select="Id"/>
        <!--Ideally, I would like to use a variable as to not repeat the same XPath throughout the code to simplify any necessary changes-->
        <xsl:variable name="productList" select="Shoppers/Shopper/Products/Product[Type != '3']"/>
        <Location>
          <Products>
            <xsl:choose>
              <xsl:when test="count($productList) &gt; 5">
                <xsl:choose>
                  <xsl:when test="count($productList[generate-id() = generate-id(key('product-distinct', concat($id,Type))[1])]) = 2">
                    <xsl:for-each select="$productList[position() &lt; 4]">
                      <xsl:call-template name="Product">
                        <xsl:with-param name="amount" select="Amount"/>
                        <xsl:with-param name="productName" select="Name"/>
                        <xsl:with-param name="type" select="Type"/>
                      </xsl:call-template>
                    </xsl:for-each>
                    <xsl:choose>
                      <xsl:when test="count($productList[position() &gt; 3][generate-id() = generate-id(key('product-distinct', concat($id,Type))[1])]) = 2">
                        <xsl:for-each select="$productList[position() &gt; 3][generate-id() = generate-id(key('product-distinct', concat($id,Type))[1])]">
                          <xsl:variable name="keyGroup" select="key('product-distinct', concat($id,Type))"/>
                          <xsl:call-template name="Product">
                            <xsl:with-param name="amount" select="sum($keyGroup/Amount)"/>
                            <xsl:with-param name="productName" select="$keyGroup"/>
                            <xsl:with-param name="type" select="$keyGroup/Type"/>
                          </xsl:call-template>
                        </xsl:for-each>
                      </xsl:when>
                      <xsl:otherwise>
                        <xsl:call-template name="Product">
                          <xsl:with-param name="amount" select="$productList[4]/Amount"/>
                          <xsl:with-param name="productName" select="$productList[4]/Name"/>
                          <xsl:with-param name="type" select="$productList[4]/Type"/>
                        </xsl:call-template>
                        <xsl:call-template name="Product">
                          <xsl:with-param name="amount" select="sum($productList[position() &gt; 4]/Amount)"/>
                          <xsl:with-param name="productName">
                            <xsl:call-template name="CombineProductNames">
                              <xsl:with-param name="products" select="$productList[position() &gt; 4]"/>
                            </xsl:call-template>
                          </xsl:with-param>
                          <xsl:with-param name="type" select="$productList[5]/Type"/>
                        </xsl:call-template>
                      </xsl:otherwise>
                    </xsl:choose>
                  </xsl:when>
                  <xsl:otherwise>
                    <xsl:for-each select="$productList[position() &lt; 5]">
                      <xsl:call-template name="Product">
                        <xsl:with-param name="amount" select="Amount"/>
                        <xsl:with-param name="productName" select="Name"/>
                        <xsl:with-param name="type" select="Type"/>
                      </xsl:call-template>
                    </xsl:for-each>
                    <xsl:call-template name="Product">
                      <xsl:with-param name="amount" select="sum($productList[position() &gt; 4]/Amount)"/>
                      <xsl:with-param name="productName">
                        <xsl:call-template name="CombineProductNames">
                          <xsl:with-param name="products" select="$productList[position() &gt; 4]"/>
                        </xsl:call-template>
                      </xsl:with-param>
                      <xsl:with-param name="type" select="$productList[5]/Type"/>
                    </xsl:call-template>
                  </xsl:otherwise>
                </xsl:choose>
              </xsl:when>
              <xsl:otherwise>
                <xsl:for-each select="$productList">
                  <xsl:call-template name="Product">
                    <xsl:with-param name="amount" select="Amount"/>
                    <xsl:with-param name="productName" select="Name"/>
                    <xsl:with-param name="type" select="Type"/>
                  </xsl:call-template>
                </xsl:for-each>
              </xsl:otherwise>
            </xsl:choose>
          </Products>
        </Location>
      </xsl:for-each>
    </Locations>
  </xsl:template>

  <xsl:template name="Product">
    <xsl:param name="amount"/>
    <xsl:param name="productName"/>
    <xsl:param name="type"/>
    <Product>
      <xsl:attribute name="Amount">
        <xsl:value-of select="format-number($amount, '0.00')"/>
      </xsl:attribute>
      <xsl:attribute name="Name">
        <xsl:value-of select="$productName"/>
      </xsl:attribute>
      <xsl:attribute name="Type">
        <xsl:value-of select="$type"/>
      </xsl:attribute>
    </Product>
  </xsl:template>

  <xsl:template name="CombineProductNames">
    <xsl:param name="products"/>
    <xsl:for-each select="$products/Name">
      <xsl:value-of select="."/>
      <xsl:if test="position() != last()">
        <xsl:text>, </xsl:text>
      </xsl:if>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

Токовый выход

<?xml version="1.0" encoding="utf-8"?>
<Locations>
  <Location>
    <Products>
      <Product Amount="5.00" Name="A" Type="1" />
      <Product Amount="10.00" Name="B" Type="2" />
      <Product Amount="20.00" Name="D" Type="1" />
      <Product Amount="25.00" Name="E" Type="2" />
      <Product Amount="105.00" Name="F, G, H" Type="1" />
    </Products>
  </Location>
  <Location>
    <Products>
      <Product Amount="5.00" Name="I" Type="A" />
      <Product Amount="10.00" Name="J" Type="B" />
      <Product Amount="25.00" Name="K" Type="A" />
      <Product Amount="30.00" Name="L" Type="B" />
      <Product Amount="35.00" Name="M" Type="B" />
    </Products>
  </Location>
</Locations>

Ожидаемый результат

<?xml version="1.0" encoding="utf-8"?>
<Locations>
  <Location>
    <Products>
      <Product Amount="5.00" Name="A" Type="1" />
      <Product Amount="10.00" Name="B" Type="2" />
      <Product Amount="20.00" Name="D" Type="1" />
      <Product Amount="60.00" Name="E, G" Type="2" />
      <Product Amount="70.00" Name="F, H" Type="1" />
    </Products>
  </Location>
  <Location>
    <Products>
      <Product Amount="5.00" Name="I" Type="A" />
      <Product Amount="10.00" Name="J" Type="B" />
      <Product Amount="25.00" Name="K" Type="A" />
      <Product Amount="30.00" Name="L" Type="B" />
      <Product Amount="35.00" Name="M" Type="B" />
    </Products>
  </Location>
</Locations>
...