1

I have the following XML and need to filter conditionally on whether or not UnitOfMeasure has a value. If the first UnitOfMeasure where ID = 'AcceptanceCriterionValue1' contains a string with a length greater than 0, I should select that value. Otherwise, I need to select the UnitOfMeasure node value where ID = 'AcceptanceCriterionValue2'. In other words, if pH1 exists, grab it. Otherwise grab pH2:

`

<MaterialLots>
  <MaterialLotProperty>
   <ID>AcceptanceCriterionValue1</ID>
   <Value>
    <ValueString>5</ValueString>
    <UnitOfMeasure>pH1</UnitOfMeasure>
   </Value>
  </MaterialLotProperty>
  <MaterialLotProperty>
   <ID>AcceptanceCriterionValue2</ID>
   <Value>
   <ValueString>7</ValueString>
   <UnitOfMeasure>pH2</UnitOfMeasure>
   </Value>
  </MaterialLotProperty>
</MaterialLots>

`
And I need to perform the following logic. The first xsl:when statement succeeds when pH1 exists, but if it's empty the xsl:otherwise fires but doesn't return pH2 for some reason:

`

    <xsl:choose>
         <xsl:when test="ns:MaterialLots/ns:MaterialLotProperty/ns:ID='AcceptanceCriterionValue1' and string-length(ns:MaterialLots/ns:MaterialLotProperty/ns:Value/ns:UnitOfMeasure) &gt; 0">
          <xsl:value-of select="ns:MaterialLots/ns:MaterialLotProperty/ns:Value/ns:UnitOfMeasure" />
        </xsl:when>
        <xsl:otherwise>
         <xsl:if test="ns:MaterialLots/ns:MaterialLotProperty/ns:ID='AcceptanceCriterionValue2' and string-length(ns:MaterialLots/ns:MaterialLotProperty/ns:Value/ns:UnitOfMeasure) &gt; 0">
          <xsl:value-of select="ns:MaterialLots/ns:MaterialLotProperty/ns:Value/ns:UnitOfMeasure" />
         </xsl:if>
        </xsl:otherwise>
</xsl:choose>

`

3 Answers3

1

I need to perform the following logic. The first xsl:when statement succeeds when pH1 exists, but if it's empty the xsl:otherwise fires but doesn't return pH2 for some reason

In general, an XPath expression that selects a node $n1 when a condition $cond is satisfied and selects a node $n2 otherwise is this:

$n1[$cond] | $n2[not($cond)]

Applied in your specific case, we substitute $n1 with:

/*/MaterialLotProperty[ID='AcceptanceCriterionValue1']/Value/UnitOfMeasure

and $cond with:

text()

And $n2 with:

/*/MaterialLotProperty[ID='AcceptanceCriterionValue2']/Value/UnitOfMeasure

Thus, one can use a single XPath expression to select the wanted node:

 /*/MaterialLotProperty[ID='AcceptanceCriterionValue1']/Value/UnitOfMeasure[text()]
|
 /*/MaterialLotProperty[ID='AcceptanceCriterionValue2']/Value/UnitOfMeasure
                      [not(/*/MaterialLotProperty[ID='AcceptanceCriterionValue1']
                                                      /Value/UnitOfMeasure[text()])]

Use this single XPath expression in this simple transformation:

<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="/">
    <xsl:value-of select=
    "/*/MaterialLotProperty[ID='AcceptanceCriterionValue1']/Value/UnitOfMeasure[text()]
    |
     /*/MaterialLotProperty[ID='AcceptanceCriterionValue2']/Value/UnitOfMeasure
                      [not(/*/MaterialLotProperty[ID='AcceptanceCriterionValue1']
                                                          /Value/UnitOfMeasure[text()])]
    "/>
  </xsl:template>
</xsl:stylesheet>

When the transformation is applied on the provided XML document:

<MaterialLots>
    <MaterialLotProperty>
        <ID>AcceptanceCriterionValue1</ID>
        <Value>
            <ValueString>5</ValueString>
            <UnitOfMeasure>pH1</UnitOfMeasure>
        </Value>
    </MaterialLotProperty>
    <MaterialLotProperty>
        <ID>AcceptanceCriterionValue2</ID>
        <Value>
            <ValueString>7</ValueString>
            <UnitOfMeasure>pH2</UnitOfMeasure>
        </Value>
    </MaterialLotProperty>
</MaterialLots>

the wanted, correct result is produced:

pH1

If you replace in the XML document

<UnitOfMeasure>pH1</UnitOfMeasure>

with:

<UnitOfMeasure/>

again the correct result is produced:

pH2
Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • This fixed it. Thank you so much for your help! I knew I didn't need a loop since I knew the exact value of the nodes to filter on, but couldn't figure out the if-else logic. XSLT can be tough! – Michael Figura May 05 '16 at 14:57
0

The expression to test for your first measure is this...

<xsl:when test="string-length(ns:MaterialLots/ns:MaterialLotProperty[ns:ID='AcceptanceCriterionValue1']/ns:Value/ns:UnitOfMeasure) > 0">

However, it might be slightly better to use a variable, to avoid some repetition.

<xsl:variable name="uom1" select="ns:MaterialLots/ns:MaterialLotProperty[ns:ID='AcceptanceCriterionValue1']/ns:Value/ns:UnitOfMeasure" />
<xsl:choose>
    <xsl:when test="string-length($uom1) > 0">
        <xsl:value-of select="$uom1" />
    </xsl:when>
    <xsl:otherwise>
        <xsl:value-of select="ns:MaterialLots/ns:MaterialLotProperty[ns:ID='AcceptanceCriterionValue2']/ns:Value/ns:UnitOfMeasure" />
    </xsl:otherwise>
</xsl:choose>

Alternatively, you could make use of a xsl:key

<xsl:key name="MLP" match="ns:UnitOfMeasure" use="../../ns:ID" />

Then you could do this

<xsl:choose>
    <xsl:when test="string-length(key('MLP', 'AcceptanceCriterionValue1')) > 0">
        <xsl:value-of select="key('MLP', 'AcceptanceCriterionValue1')" />
    </xsl:when>
    <xsl:otherwise>
        <xsl:value-of select="key('MLP', 'AcceptanceCriterionValue2')" />
    </xsl:otherwise>
</xsl:choose>
Tim C
  • 70,053
  • 14
  • 74
  • 93
0

You'll need to iterate through each MaterialLotProperty. Also, check length of UnitOfMeasure first, before the choose.

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns="foo">
<xsl:output method="xml"/>
<xsl:template match="/">
<xml>
    <xsl:for-each select='//ns:MaterialLotProperty'>
        <xsl:if test='string-length(ns:Value/ns:UnitOfMeasure) &gt; 0'>
            <xsl:element name='output'>
                <xsl:choose>
                    <xsl:when test="(ns:ID='AcceptanceCriterionValue1')">
                        <xsl:value-of select='ns:Value/ns:UnitOfMeasure'/>
                    </xsl:when>
                    <xsl:when test="(ns:ID='AcceptanceCriterionValue2')">
                        <xsl:value-of select='ns:Value/ns:UnitOfMeasure'/>
                    </xsl:when>
                </xsl:choose>
            </xsl:element>
        </xsl:if>
    </xsl:for-each>
</xml>

</xsl:template>
</xsl:stylesheet>

This second XSL outputs the first non-blank UnitOfMeasure.

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns="foo">
<xsl:output method="xml"/>
<xsl:template match="/">
<xml>
    <xsl:for-each select='//ns:MaterialLotProperty/ns:Value[string-length( ns:UnitOfMeasure ) &gt; 0]/ns:UnitOfMeasure'>
        <xsl:if test='(position() =1)'>
            <xsl:element name='output'>
                <xsl:value-of select='.'/>
            </xsl:element>
        </xsl:if>
    </xsl:for-each>
</xml>

</xsl:template>
</xsl:stylesheet>
William Walseth
  • 2,803
  • 1
  • 23
  • 25
  • I like your solution. Unfortunately, when both values pH1 and pH2 both exist, it's grabbing both of the values. I only want the first one if it exists and the second one if the first one is an empty string. This is the issue I've been facing all along. I can get the first value and the second value and both if they both exist. But I can't figure out how to get one or the other but not both. – Michael Figura May 04 '16 at 20:13