1

I'm very much a beginner with xsl transformations

I have some xml that I need to insert an attribute into an element when that attribute doesn't exist..

Using the below xml as an example.

<Order Id="IR1598756" Status="2">
  <Details>
    <SomeInfo>Sample Data</SomeInfo>
  </Details>
  <Documents>
    <Invoice>
      <Date>15-02-2011</Date>
      <Time>11:22</Time>
      <Employee Id="159">James Morrison</Employee>
    </Invoice>
    <DeliveryNote>
      <Reference>DN1235588</Reference>
      <HoldingRef>HR1598785</HoldingRef>
      <Date>16-02-2011</Date>
      <Time>15:00</Time>
      <Employee Id="25">Javi Cortez</Employee>
    </DeliveryNote>
  </Documents>
</Order>

Desired Output

<Order Id="IR1598756" Status="2">
  <Details>
    <SomeInfo>Sample Data</SomeInfo>
  </Details>
  <Documents>
    <Invoice Id="DN1235588">
      <Date>15-02-2011</Date>
      <Time>11:22</Time>
      <Employee Id="159">James Morrison</Employee>
    </Invoice>
  </Documents>
</Order>    

The <Invoice> element can have an Id attribute <Invoice Id="IR1564897">

How can I check the following.

  1. Check that the attribute exists
  2. If not then Insert the Value of the <Refernce>DN1235588</Reference> as the Id
  3. If there is no <Reference> Use the Value of the <HoldingRef>HR1598785</HoldingRef>

I was looking at implementing something like the following

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

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

  <xsl:template match="Order/Documents/Invoice[not(@Id)]">
      <xsl:attribute name="Id">
        <xsl:value-of select="//Documents/DeliveryNote/Reference"/>
      </xsl:attribute>      
  </xsl:template>

The above is not outputting the full <Invoice> element. How can I correct this?

  <xsl:if test="Order/Documents/DeliveryNote/Reference">
    <xsl:value-of select="//Documents/DeliveryNote/Reference"/>
  </xsl:if>
  <xsl:if test="Not(Order/Documents/DeliveryNote/Reference)">
    <xsl:value-of select="//Documents/DeliveryNote/HoldingRef"/>
  </xsl:if>

If either one will always exist will this work to alternate between <Reference> and <HoldingRef>?

With the help of Alex: The following has worked for me to replace the attribute

  <xsl:template match="Order/Documents/Invoice[not(@Id)]">       
    <Invoice>
      <xsl:attribute name="Id">         
        <xsl:value-of Select="//Documents/DeliveryNote/Reference"/>
      </xsl:attribute>         
      <xsl:apply-templates select="@* | node()"/>
    </Invoice>
  </xsl:template>
user617850
  • 35
  • 1
  • 8
  • @user617850: Do note that your solution is wrong: it will always **add the same @Id value** for every `Invoice` element without `@Id`. –  Feb 17 '11 at 13:24

3 Answers3

6

The shortest answer:

<xsl:template match="Invoice[not(@Id)]">
    <Invoice Id="{(../DeliveryNote/Reference|
                   ../DeliveryNote/HoldingRef)[1]}">
        <xsl:apply-templates select="@* | node()"/>
    </Invoice>
</xsl:template>
0

Give a try:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <xsl:apply-templates select="//Order"/>
    </xsl:template>

    <!-- Match invoices without ids -->
    <!-- [DeliveryNote[Reference or HoldingRef] - defend against empty attributes -->
    <xsl:template match="Invoice[not(@id)][DeliveryNote[Reference or HoldingRef]]">
        <xsl:copy>
            <!-- create an attribute and fetch required data. In case Reference is present then insert reference
             otherwise - HoldingRef -->
            <xsl:attribute name="Id">
                <xsl:value-of select="following-sibling::DeliveryNote[1]/Reference |
                        following-sibling::DeliveryNote[1]/HoldingRef"/>
            </xsl:attribute>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="//DeliveryNote"/>

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

As for your questions:

The above is not outputting the full element. How can I correct this?

See my example.

If either one will always exist will this work to alternate between <Reference> and <HoldingRef>?

Either with XPath (as in my example) or with xsl:choose.

Alex Nikolaenkov
  • 2,505
  • 20
  • 27
0

What about this?

  <xsl:template match="Invoice[not(@Id)]">
    <xsl:element name="Invoice">
      <xsl:attribute name="Id">
        <xsl:variable name="REF" select="../DeliveryNote/Reference"/>
        <xsl:choose>
          <xsl:when test="not($REF)">
            <xsl:value-of select="../DeliveryNote/HoldingRef"/>
          </xsl:when>
          <xsl:otherwise>
            <xsl:value-of select="../DeliveryNote/Reference"/>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:attribute>
      <xsl:apply-templates select="*"/>
    </xsl:element>
  </xsl:template>

Use it instead your <xsl:template match="Order/Documents/Invoice[not(@Id)]">

Alex Zhevzhik
  • 3,347
  • 19
  • 19
  • @alex, your version will insert empty attributes when `HoldingRef` and `Reference` are not present. And it seems that `xsl:element` is redundant because you can use `xsl:copy` instead. As a side-effect y ou are loosing all the attributes from the original `Invoice` element because you are not applying them. – Alex Nikolaenkov Feb 17 '11 at 12:36
  • Thank you, you are absolutely correct. The reason why I don't care about absence of both attributes is that @user617850 told that presence of one of them is guaranteed. Next, "copy" vs "element". Of course, I should admit that "copy" looks more tidy. But asker has lost "Invoice" tag, so I've preferred to point concrete place where mistake is covered. All attrs from Invoice tag? The question wasn't about it, and all of us recognize that this could be fixed with simple string "@* | *" – Alex Zhevzhik Feb 17 '11 at 12:57
  • `parent::node()` could be just `..` . When you known the QName, it's better to use Literal Result Element instead of `xsl:element`. It's not a good idea to use those absolute expressions `//Documents/DeliveryNote/HoldingRef` and `//Documents/DeliveryNote/Reference`... –  Feb 17 '11 at 13:21