We help IT Professionals succeed at work.

Creating an element based on double condition with XSLT

Adam Lee
Adam Lee asked
on
169 Views
Last Modified: 2018-02-02
I'm having a hard time wrapping my head around the syntax I need in order to create an element I want. Brand new to XML/XSLT and not sure if this is the right approach.

I'm trying to parse my XML data file into element-centric such that I can format the data into a readable structure in an access database.

I'm trying to pull a data value from inside an element, based on two conditions.

The data is inside an element named 'Reading' and the data is labeled 'value'. Above the 'Reading' element is the defining element named 'ConsumptionSpec'.

What I'm trying to test is what unit of measurement (UOM) the current 'ConsumptionSpec' is on, and THEN, test another attribute named 'TouBucket' that holds a value of either 'TierA', 'TierB'/C/D or Total. The UOM can hold "kWh, kW, kVAh, or kVA". I'm trying to get the first one laid out as I'm going to repeat this test for making elements for each Tier (A through D) and the Total. (Trying to give as clear of an explanation as I can)

Currently I'm trying to utilize xsl:for-each to choose the ConsumptionSpec right above Reading, and then use xsl:when to test the UOM and TouBucket seperately. After the test I create an element and I'm attempting to pull the value of the current Reading element.

Here is an excerpt from my XML so you can see what values I'm trying to step through during testing.

    <MeterReadings Irn="Null" Source="Remote" SourceName="Null" SourceIrn="Null" Initiator="Schedule" Purpose="Null" CollectionTime="2017-04-01 09:00:00" >
		<Meter MeterIrn="Null" MeterName="Null" IsActive="true" SerialNumber="Null" MeterType="A3_ILN" Description="" InstallDate="2017-01-21 05:00:00" RemovalDate="" AccountIdent="Null" AccountName="" SdpIdent="" Location="Null" TimeZoneIndex="Null" Timezone="Null" TimeZoneOffset="300" ObservesDaylightSavings="false" MediaType="900 MHz" />

		<ReadingQualityIndicator Name="Tamper Alert" Value="true" />

		<ConsumptionData >

			<ConsumptionSpec UOM="kWh" Direction="Delivered" TouBucket="Total" MeasurementPeriod="Current" Multiplier="1" />

			<Reading TimeStamp="2017-04-01 03:08:00" Value="902" />

		</ConsumptionData>

		<ConsumptionData >

			<ConsumptionSpec UOM="kWh" Direction="Delivered" TouBucket="TierA" MeasurementPeriod="Current" Multiplier="1" />

			<Reading TimeStamp="2017-04-01 03:08:00" Value="0" />

		</ConsumptionData>

		<ConsumptionData >

			<ConsumptionSpec UOM="kWh" Direction="Delivered" TouBucket="TierB" MeasurementPeriod="Current" Multiplier="1" />

			<Reading TimeStamp="2017-04-01 03:08:00" Value="0" />

		</ConsumptionData>

		<ConsumptionData >

			<ConsumptionSpec UOM="kWh" Direction="Delivered" TouBucket="TierC" MeasurementPeriod="Current" Multiplier="1" />

			<Reading TimeStamp="2017-04-01 03:08:00" Value="902" />

		</ConsumptionData>

		<ConsumptionData >

			<ConsumptionSpec UOM="kWh" Direction="Delivered" TouBucket="TierD" MeasurementPeriod="Current" Multiplier="1" />

			<Reading TimeStamp="2017-04-01 03:08:00" Value="0" />

		</ConsumptionData>

Open in new window





And here is my current XSLT

        <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
          <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
          <xsl:strip-space elements="*"/>
        
          <!-- BY DEFAULT, elements and text nodes are copied,
               and elements' attributes and contents are transformed as child nodes
               of the output element -->
          <xsl:template match="node()">
            <xsl:copy>
              <xsl:apply-templates select="@* | node()"/>
            </xsl:copy>
          </xsl:template>
        
          <!-- By default, attributes are transformed to elements -->
          <xsl:template match="@*">
            <xsl:element name="{name()}">
              <xsl:value-of select="."/>
            </xsl:element>
          </xsl:template>
          
          
          <!-- Certain elements have only their contents transformed -->
          <xsl:template match="
        		Meter | Status | ConsumptionData |
        		Statuses | MaxDemandData | MaxDemandSpec |
        		InstrumentationValue | IntervalData | IntervalSpec">
            <!-- no xsl:copy, and attribute children, if any, are ignored -->
            <xsl:apply-templates select="@* | node()"/>
          </xsl:template>
        
          
          <!-- 
          Applies an extra element tag to the selected match
    
    	
          and pulls the value from the MeterReading ancestor it's
          tagged under.
          -->
    
       <xsl:template match="Reading">
       
    		<xsl:copy>
    		
    			<xsl:element name="MeterReadingIRN">
    				<xsl:value-of select="ancestor::MeterReadings/@Irn"/>
    			</xsl:element>
    			
    			  <!-- 
    			  Trying to get into the ConsumptionSpec tag it's related to,
    			  then test what the unit of measurement is (UOM),
    			  and then test what 'TouBucket' it is a part of (TierA/B/C/D or Total),
    			  and THEN create a new element so that I can hold the 'value' that is inside
    			  the Reading element, so that it will be referenced to that specific UOM.
    			  -->
    			<xsl:for-each select="ancestor::ConsumptionSpec">
    				<xsl:choose>
    					<xsl:when test="@UOM='kWh'">
    						<xsl:when test="@TouBucket='Total'">
    						
    							<xsl:element name="kWhTotal">
    								<xsl:value-of select="Reading/@Value"/>
    							</xsl:element>
    							
    						</xsl:when>
    					</xsl:when>
    					<!-- Not sure how I can make my otherwise into a useful element here -->
    					<xsl:otherwise>
    					
    						<xsl:element name="BlankTest">
    							<xsl:value-of select="ancestor::MeterReadings/@Irn"/>
    						</xsl:element>
    						
    					</xsl:otherwise>
    				</xsl:choose>
    			</xsl:for-each>  
    			<xsl:apply-templates select="@*|node()"/>
    		</xsl:copy>
    	</xsl:template>
     
      <xsl:template match="Channel">
      <xsl:copy>
      <xsl:element name="MeterReadingIRN">
         <xsl:value-of select="ancestor::MeterReadings/@Irn"/>
       </xsl:element>
       <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
     </xsl:template>
     
    </xsl:stylesheet>

Open in new window



Any and all suggestions are appreciated, been banging my head against this most of Easter weekend! Let me know if there's more info I can provide to make it more understandable.
Comment
Watch Question

Gertone (Geert Bormans)Information Architect
CERTIFIED EXPERT
Top Expert 2006

Commented:
<xsl:when test="@UOM='kWh'">
                        <xsl:when test="@TouBucket='Total'">

should be

                    <xsl:when test="@UOM='kWh'  and @TouBucket='Total'">

after that you can have a second xsl:when

                    <xsl:when test="@UOM='kWh'">
to cover that other condition

when are branches, you can't nest them
Gertone (Geert Bormans)Information Architect
CERTIFIED EXPERT
Top Expert 2006

Commented:
But a lot is going wrong in th estylesheet
the ancestor::ConsumptionSpec is not an ancestor but a sibling

try to test for
preceding-sibling::ConsumptionSpec
instead
Gertone (Geert Bormans)Information Architect
CERTIFIED EXPERT
Top Expert 2006

Commented:
and you don't need a for-each because there is only one such value

drop the for-each en,tirely and have

                <xsl:choose>
                    <xsl:when test="preceding-sibling::ConsumptionSpec/@UOM='kWh'
                        and preceding-sibling::ConsumptionSpec/@TouBucket='Total'">
                           
                            <xsl:element name="kWhTotal">
                                <xsl:value-of select="Reading/@Value"/>
                            </xsl:element>
                           
                     </xsl:when>
Gertone (Geert Bormans)Information Architect
CERTIFIED EXPERT
Top Expert 2006

Commented:
If I understand well what you need, you should think on a higher level in the hierarchy

decisions should be made on the ConsumptionData level, not on the Reading level

you should filter higher up the tree

I would definitely drop the choose in favor of templates like this

   <xsl:template match="ConsumptionData[ConsumptionSpec/@UOM = 'kWh'][ConsumptionSpec/@TouBucket = 'Total']/Reading">
        <xsl:element name="kWhTotal">
            <xsl:value-of select="@Value"/>
        </xsl:element>
    </xsl:template>

Open in new window


that is absolutely more elegant and a hell of a lot easier to maintain
Gertone (Geert Bormans)Information Architect
CERTIFIED EXPERT
Top Expert 2006

Commented:
You could by the way have dynamic element names

    <xsl:template match="Reading">
        <xsl:copy>
            <xsl:element name="MeterReadingIRN">
                <xsl:value-of select="ancestor::MeterReadings/@Irn"/>
            </xsl:element>
            <xsl:element name="{preceding-sibling::ConsumptionSpec/@UOM}{preceding-sibling::ConsumptionSpec/@TouBucket}">
                <xsl:value-of select="@Value"/>
            </xsl:element>
        </xsl:copy>
    </xsl:template>
Adam LeeStudent Programmer

Author

Commented:
Geert, again you've exposed me to a breadth of knowledge. Taking a look into your comments now, thank you very very much.
Adam LeeStudent Programmer

Author

Commented:
@Geert , I'm utilizing                   

 <xsl:element name="{preceding-sibling::ConsumptionSpec/@UOM}{preceding-sibling::ConsumptionSpec/@TouBucket}">
                 <xsl:value-of select="@Value"/>
            </xsl:element>

Open in new window


To help myself understand it better.

 I'm running into an issue though, it dynamically creates element names exactly how I want it to, it steps through each tier and pulls the value dynamically, although after the first set of values it runs into an error and wont produce any more lines of xml.

XMLError.PNG
In the image here I've snipped where it's hitting the break. As you can see it stops shortly after "TierD". Currently I'm using Internet Explorer as my XML/XSLT processor because of the restrictions on downloads/installations at my workplace, and purely out of incompetence probably. Is this what's causing my error? I know I've read on another resource that internet Explorer can only handle XML version 1.0.
Gertone (Geert Bormans)Information Architect
CERTIFIED EXPERT
Top Expert 2006

Commented:
Internet Explorer can only handle XSLT1.0 as oposed to the current commonly used XSLT2.0
XML never got further then 1.0 anyway

the code will crash if the name generated dynamically
- is empty
- starts with a number
- contains spaces
-...
so if both attributes are missing, you have a problem
Information Architect
CERTIFIED EXPERT
Top Expert 2006
Commented:
Unlock this solution and get a sample of our free trial.
(No credit card required)
UNLOCK SOLUTION
Unlock the solution to this question.
Thanks for using Experts Exchange.

Please provide your email to receive a sample view!

*This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

OR

Please enter a first name

Please enter a last name

8+ characters (letters, numbers, and a symbol)

By clicking, you agree to the Terms of Use and Privacy Policy.