I need an XSL transform to insert a child element of the selected node using data from an ancestor

Question

My XML looks like this:

<SCL>
  <IED name="D60_220SW1">
    <AccessPoint name="S1">
      <Server>
        <LDevice inst="Master">
          <LN0 lnClass="LLN0" inst="" lnType="LLN0_0">
            <ReportControl name="URCB01" rptID="XYZ" >
              <TrgOps dchg="true" />
              <OptFields configRef="true" />
             <RptEnabled max="1" />
            </ReportControl>
            <ReportControl name="URCB02" rptID="PAC" datSet="PAC" >
              <TrgOps dchg="true" qchg="true" period="true" />
              <OptFields configRef="true" />
             <RptEnabled max="1" />
            </ReportControl>
          </LN0>
        </LDevice>
      </Server>
    </AccessPoint>
  </IED>
</SCL>

I need a transform to select all ReportControl nodes where @rptID="PAC" and insert a child node of the RptEnabled node with attribute values dependent on the value of the name attribute of the IED ancestor node.

The result should look like this:

<SCL>
  <IED name="D60_220SW1">
    <AccessPoint name="S1">
      <Server>
        <LDevice inst="Master">
          <LN0 lnClass="LLN0" inst="" lnType="LLN0_0">
            <ReportControl name="URCB01" rptID="XYZ" >
              <TrgOps dchg="true" />
              <OptFields configRef="true" />
             <RptEnabled max="1" />
            </ReportControl>
            <ReportControl name="URCB02" rptID="PAC" datSet="PAC" >
              <TrgOps dchg="true" qchg="true" period="true" />
              <OptFields configRef="true" />
              <RptEnabled max="1" >
                <ClientLN iedName="APACC_1" apRef="S2" ldInst="LD0" lnClass="ITCI" lnInst="1" />
              </RptEnabled>
            </ReportControl>
          </LN0>
        </LDevice>
      </Server>
    </AccessPoint>
  </IED>
</SCL>

The values of the iedName and apRef attributes on the inserted ClientLN node are determined by the value of the name attribute on the IED ancestor node. Like this (I'm using C# pseudo code to illustrate):

iedName="APACC_" + name.Contains("_220") ? "1": "2"

I'm sure this is doable, but I can't figure out how, and I wasn't able to find an applicable example.


Show source
| xml   | xslt   2016-12-30 19:12 1 Answers

Answers ( 1 )

  1. 2016-12-30 19:12

    The following XSLT does the job. But because you did not provide a rule for generating the other attributes of the ClientLN node, I (so far) just copied them from your desired output. It should be easy for you to fill them with the desired values.

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output method="xml" />
    
      <xsl:template match="node()|@*">                       <!-- identity transform -->
        <xsl:copy>
          <xsl:apply-templates select="node()|@*" />
        </xsl:copy>
      </xsl:template>
    
      <xsl:template match="ReportControl[@rptID = 'PAC']">   <!-- replace special nodes -->
        <xsl:copy>
          <xsl:apply-templates select="node()[not(self::RptEnabled)]|@*" />
          <RptEnabled>
            <xsl:copy-of select="RptEnabled/@*" />
            <ClientLN apRef="S2" ldInst="LD0" lnClass="ITCI" lnInst="1">
              <xsl:attribute name="iedName">
                <xsl:choose>
                  <xsl:when test="contains(ancestor::IED/@name,'_220')">APACC_1</xsl:when>
                  <xsl:otherwise>APACC_2</xsl:otherwise>
                </xsl:choose>
              </xsl:attribute>
            </ClientLN>
          </RptEnabled>
        </xsl:copy>    
      </xsl:template>     
    </xsl:stylesheet>
    
◀ Go back