0
votes

I have got a sample xml file where root has element with name "element". This elements can be nested.

I would like to exclude elements with name "position" where "position" value x = ("number" + "another") * count is greater than average of sum(("number" + "another") * "count") from all "position" elements.

How to process this xml file with xslt v 1.

<?xml version="1.0" encoding="utf-8" ?>
<root>
  <element>
    <position>
      <number>
        1
      </number>
      <another>
        2
      </another>
      <count>
        3
      </count>
    </position>
    <position>
      <number>
        3
      </number>
      <another>
        1
      </another>
      <count>
        5
      </count>
    </position>
    <element>
      <position>
        <number>
          3
        </number>
        <another>
          3
        </another>
        <count>
          5
        </count>
      </position>
      <position>
        <number>
          3
        </number>
        <another>
          6
        </another>
        <count>
          5
        </count>
      </position>
      <element>
        <position>
          <number>
            3
          </number>
          <another>
            3
          </another>
          <count>
            5
          </count>
        </position>
        <position>
          <number>
            3
          </number>
          <another>
            7
          </another>
          <count>
            5
          </count>
        </position>
        <element>
          <position>
            <number>
              33
            </number>
            <another>
              4
            </another>
            <count>
              5
            </count>
          </position>
          <position>
            <number>
              34
            </number>
            <another>
              3
            </another>
            <count>
              5
            </count>
          </position>
        </element>
      </element>
    </element>
  </element>
  <element>
    <position>
      <number>
        5
      </number>
      <another>
        1
      </another>
      <count>
        2
      </count>
    </position>
    <position>
      <number>
        3
      </number>
      <another>
        3
      </another>
      <count>
        9
      </count>
    </position>
    <element>
      <position>
        <number>
          5
        </number>
        <another>
          3
        </another>
        <count>
          2
        </count>
      </position>
      <position>
        <number>
          3
        </number>
        <another>
          3
        </another>
        <count>
          5
        </count>
      </position>
    </element>
  </element>
</root>
2

2 Answers

2
votes

I would use the following two-pass approach:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
 <xsl:output omit-xml-declaration="yes"/>

 <xsl:variable name="vrtfPass1">
  <xsl:apply-templates select="/*" mode="getScore"/>
 </xsl:variable>

 <xsl:variable name="vPass1" select="ext:node-set($vrtfPass1)/*"/>

 <xsl:variable name="vAverage" select=
  "sum($vPass1//position/@score) div count($vPass1//position)"/>

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

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

 <xsl:template match="/">
  <xsl:apply-templates select="$vPass1"/>
 </xsl:template>

 <xsl:template match="position" mode="getScore">
  <position score="{(number + another)*count}">
    <xsl:apply-templates mode="getScore"/>
  </position>
 </xsl:template>

 <xsl:template match="position">
  <xsl:if test="not(@score > $vAverage)">
   <position>
    <xsl:apply-templates mode="getScore"/>
   </position>
  </xsl:if>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<root>
    <element>
        <position>
            <number>1</number>
            <another>2</another>
            <count>3</count>
        </position>
        <position>
            <number>3</number>
            <another>1</another>
            <count>5</count>
        </position>
        <element>
            <position>
                <number>3</number>
                <another>3</another>
                <count>5</count>
            </position>
            <position>
                <number>3</number>
                <another>6</another>
                <count>5</count>
            </position>
            <element>
                <position>
                    <number>3</number>
                    <another>3</another>
                    <count>5</count>
                </position>
                <position>
                    <number>3</number>
                    <another>7</another>
                    <count>5</count>
                </position>
                <element>
                    <position>
                        <number>33</number>
                        <another>4</another>
                        <count>5</count>
                    </position>
                    <position>
                        <number>34</number>
                        <another>3</another>
                        <count>5</count>
                    </position>
                </element>
            </element>
        </element>
    </element>
    <element>
        <position>
            <number>5</number>
            <another>1</another>
            <count>2</count>
        </position>
        <position>
            <number>3</number>
            <another>3</another>
            <count>9</count>
        </position>
        <element>
            <position>
                <number>5</number>
                <another>3</another>
                <count>2</count>
            </position>
            <position>
                <number>3</number>
                <another>3</another>
                <count>5</count>
            </position>
        </element>
    </element>
</root>

the wanted, correct result is produced:

<root>
    <element>
        <position>
            <number>1</number>
            <another>2</another>
            <count>3</count>
        </position>
        <position>
            <number>3</number>
            <another>1</another>
            <count>5</count>
        </position>
        <element>
            <position>
                <number>3</number>
                <another>3</another>
                <count>5</count>
            </position>
            <position>
                <number>3</number>
                <another>6</another>
                <count>5</count>
            </position>
            <element>
                <position>
                    <number>3</number>
                    <another>3</another>
                    <count>5</count>
                </position>
                <position>
                    <number>3</number>
                    <another>7</another>
                    <count>5</count>
                </position>
                <element>


                </element>
            </element>
        </element>
    </element>
    <element>
        <position>
            <number>5</number>
            <another>1</another>
            <count>2</count>
        </position>
        <position>
            <number>3</number>
            <another>3</another>
            <count>9</count>
        </position>
        <element>
            <position>
                <number>5</number>
                <another>3</another>
                <count>2</count>
            </position>
            <position>
                <number>3</number>
                <another>3</another>
                <count>5</count>
            </position>
        </element>
    </element>
</root>
0
votes

Just for fun, this stylesheet without extensions:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:variable name="vAvgPosition">
        <xsl:apply-templates select="/descendant::position[1]"
                             mode="avgPosition"/>
    </xsl:variable>
    <xsl:template match="node()|@*" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="position" mode="avgPosition">
        <xsl:param name="pSum" select="0"/>
        <xsl:param name="pCount" select="0"/>
        <xsl:variable name="vSum"
                      select="$pSum + (number + another) * count"/>
        <xsl:variable name="vCount" select="$pCount + 1"/>
        <xsl:variable name="vNext" select="following::position[1]"/>
        <xsl:apply-templates select="$vNext" mode="avgPosition">
            <xsl:with-param name="pSum" select="$vSum"/>
            <xsl:with-param name="pCount" select="$vCount"/>
        </xsl:apply-templates>
        <xsl:if test="not($vNext)">
            <xsl:value-of select="$vSum div $vCount"/>
        </xsl:if>
    </xsl:template>
    <xsl:template match="position">
        <xsl:if test="not((number + another) * count > $vAvgPosition)">
            <xsl:call-template name="identity"/>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

Output:

<root>
    <element>
        <position>
            <number>1</number>
            <another>2</another>
            <count>3</count>
        </position>
        <position>
            <number>3</number>
            <another>1</another>
            <count>5</count>
        </position>
        <element>
            <position>
                <number>3</number>
                <another>3</another>
                <count>5</count>
            </position>
            <position>
                <number>3</number>
                <another>6</another>
                <count>5</count>
            </position>
            <element>
                <position>
                    <number>3</number>
                    <another>3</another>
                    <count>5</count>
                </position>
                <position>
                    <number>3</number>
                    <another>7</another>
                    <count>5</count>
                </position>
                <element></element>
            </element>
        </element>
    </element>
    <element>
        <position>
            <number>5</number>
            <another>1</another>
            <count>2</count>
        </position>
        <position>
            <number>3</number>
            <another>3</another>
            <count>9</count>
        </position>
        <element>
            <position>
                <number>5</number>
                <another>3</another>
                <count>2</count>
            </position>
            <position>
                <number>3</number>
                <another>3</another>
                <count>5</count>
            </position>
        </element>
    </element>
</root>

Look how compact an XSLT 2.0 solution is:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:variable name="vAvgPosition"
         select="avg(//position/((number+another)*count))"/>
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="position[(number+another)*count > $vAvgPosition]"/>
</xsl:stylesheet>