XSLT 1.0 Grouping

2020-05-07 06:43发布

问题:

I need to apply XSLT 1.0 transformation to following XML:

<ProfitLossFinancials>
  <ProfitLossFinancial>
    <Year>2013</Year>
    <Turnover>13</Turnover>
    <Profit>13</Profit>
  </ProfitLossFinancial>
  <ProfitLossFinancial>
    <Year>2012</Year>
    <Turnover>12</Turnover>
    <Profit>12</Profit>
  </ProfitLossFinancial>
</ProfitLossFinancials>
<BalanceSheetFinancials>
  <BalanceSheetFinancial>
    <Year>2013</Year>
    <FixedAssets>13</FixedAssets>
  </BalanceSheetFinancial>
  <BalanceSheetFinancial>
    <Year>2011</Year>
    <FixedAssets>11</FixedAssets>
  </BalanceSheetFinancial>
</BalanceSheetFinancials>

to have following output:

<Financials>
  <Financial>
    <Year>2013</Year>
    <Turnover>13</Turnover>
    <Profit>13</Profit>
    <FixedAssets>13</FixedAssets>
  </Financial>
  <Financial>
    <Year>2012</Year>
    <Turnover>12</Turnover>
    <Profit>12</Profit>
    <FixedAssets/>
  </Financial>
  <Financial>
    <Year>2011</Year>
    <Turnover/>
    <Profit/>
    <FixedAssets>11</FixedAssets>
  </Financial>
</Financials>

How to do that?

回答1:

Please try this:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
  <xsl:key name="kFinancialByYear"
           match="*[self::ProfitLossFinancial or self::BalanceSheetFinancial]"
           use="Year" />
  <xsl:variable name="af" 
                select="//*[self::ProfitLossFinancial or 
                            self::BalanceSheetFinancial]"/>

  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="/*">
    <Financials>
      <xsl:apply-templates select="$af[generate-id() = 
                                      generate-id(key('kFinancialByYear', Year)[1])]"
                           mode="group" />
    </Financials>
  </xsl:template>

  <xsl:template match="*" mode="group">
    <xsl:variable name="thisYearValues" select="key('kFinancialByYear', Year)" />
    <Financial>
      <xsl:apply-templates select="Year" />
      <Turnover>
        <xsl:value-of select="$thisYearValues/Turnover"/>
      </Turnover>
      <Profit>
        <xsl:value-of select="$thisYearValues/Profit"/>
      </Profit>
      <FixedAssets>
        <xsl:value-of select="$thisYearValues/FixedAssets"/>
      </FixedAssets>
    </Financial>
  </xsl:template>
</xsl:stylesheet>

When run on this input:

<n>
  <ProfitLossFinancials>
    <ProfitLossFinancial>
      <Year>2013</Year>
      <Turnover>13</Turnover>
      <Profit>13</Profit>
    </ProfitLossFinancial>
    <ProfitLossFinancial>
      <Year>2012</Year>
      <Turnover>12</Turnover>
      <Profit>12</Profit>
    </ProfitLossFinancial>
  </ProfitLossFinancials>
  <BalanceSheetFinancials>
    <BalanceSheetFinancial>
      <Year>2013</Year>
      <FixedAssets>13</FixedAssets>
    </BalanceSheetFinancial>
    <BalanceSheetFinancial>
      <Year>2011</Year>
      <FixedAssets>11</FixedAssets>
    </BalanceSheetFinancial>
  </BalanceSheetFinancials>
</n>

The result is:

<Financials>
  <Financial>
    <Year>2013</Year>
    <Turnover>13</Turnover>
    <Profit>13</Profit>
    <FixedAssets>13</FixedAssets>
  </Financial>
  <Financial>
    <Year>2012</Year>
    <Turnover>12</Turnover>
    <Profit>12</Profit>
    <FixedAssets></FixedAssets>
  </Financial>
  <Financial>
    <Year>2011</Year>
    <Turnover></Turnover>
    <Profit></Profit>
    <FixedAssets>11</FixedAssets>
  </Financial>
</Financials>


回答2:

Here are more straightforward solution using for-each and key:

<?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:key match="Year" name="kyears" use="."/>

    <xsl:template match="/*">
        <Financials>
            <xsl:for-each select="//Year[generate-id() = 
                                    generate-id(key('kyears', .)[1])]"  >
                <xsl:variable name="year" select="." />
                <xsl:variable name="yearData" select="key('kyears', $year)/.." />
                <Financial>
                    <xsl:copy-of select="$year"/>
                    <Turnover>
                        <xsl:value-of select="$yearData/Turnover"/>
                    </Turnover>
                    <Profit>
                        <xsl:value-of select="$yearData/Profit"/>
                    </Profit>
                    <FixedAssets>
                        <xsl:value-of select="$yearData/FixedAssets"/>
                    </FixedAssets>
                </Financial>
            </xsl:for-each>
        </Financials>
    </xsl:template>

</xsl:stylesheet>

Which generate the following output:

<?xml version="1.0"?>
<Financials>
  <Financial>
    <Year>2013</Year>
    <Turnover>13</Turnover>
    <Profit>13</Profit>
    <FixedAssets>13</FixedAssets>
  </Financial>
  <Financial>
    <Year>2012</Year>
    <Turnover>12</Turnover>
    <Profit>12</Profit>
    <FixedAssets/>
  </Financial>
  <Financial>
    <Year>2011</Year>
    <Turnover/>
    <Profit/>
    <FixedAssets>11</FixedAssets>
  </Financial>
</Financials>


回答3:

<xsl:template match="ProfitLossFinancials">
    <Financials>
        <xsl:apply-templates select="ProfitLossFinancial"/>
    </Financials>
</xsl:template>

<xsl:template match="ProfitLossFinancial">
    <Financial>
        <xsl:copy-of select="./*"/>
        <xsl:copy-of select="../../BalanceSheetFinancials/BalanceSheetFinancial[Year = current()/Year]/FixedAssets"/>
    </Financial>
</xsl:template>