0

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?

JLRishe
  • 99,490
  • 19
  • 131
  • 169
jlp
  • 9,800
  • 16
  • 53
  • 74

3 Answers3

2

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>
JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • @NilsWerner That's because you made a much shorter answer that doesn't work. – JLRishe Jun 11 '13 at 14:04
  • @NilsWerner - this is a correct solution, as it produces the wanted output. It takes into account all possible fields for each possible year. – ABach Jun 11 '13 at 14:04
  • when ProfitLossFinancial and BalanceSheetFinancial both contain an element with the same name - can we distinguish them in Financial item? – jlp Jun 12 '13 at 06:37
  • @jlp: To distinguish same element names from different parent use `self::NAME`. E.g: for Turnover from ProfitLossFinancial: `"$thisYearValues/self::ProfitLossFinancial/Turnover"`in the soltion above or `` in my soltuion. – hr_117 Jun 12 '13 at 09:12
1

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>
hr_117
  • 9,589
  • 1
  • 18
  • 23
-2
<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>
Nils Werner
  • 34,832
  • 7
  • 76
  • 98
  • This does not produce the wanted output. – ABach Jun 11 '13 at 13:50
  • Small typo, fixed it. Also, more helpful error descriptions are appreciated. – Nils Werner Jun 11 '13 at 13:55
  • Unfortunately, this is more than a small typo. If these are the only two templates you propose, then all the `text()` elements are output directly to the document; additionally, this only produces two `` blocks of elements - a block for 2011 is missing. – ABach Jun 11 '13 at 14:00
  • The FixedAssets element for 2013 is also missing. – JLRishe Jun 11 '13 at 14:05
  • I don't try to give complete out of the box solutions but instead building blocks that the OP has to integrate into his solution. It should be obvious to anyone that the declaration, `xsl:stylesheet` and other templates are missing. But you are right, I missed the fact, that 2011 doesnt have a `ProfitLossFinancial` element... – Nils Werner Jun 11 '13 at 14:16
  • No one is asking that you add the XSLT preamble; we certainly understand not wanting to provide such "building blocks". However, if crucial logic is going to be missing (and I would certainly classify "missing templates" as that logic) and you want that to be an exercise for the OP, my recommendation would be to make that clear in your answer. – ABach Jun 11 '13 at 14:18