Search code examples
xmlxslt

Repeat XSLT for unkown number of elements


I am using XSLT to change XML coming from web order softare, in order to map the data to a management software within our company. I've been successful with all of the header info, but now I'm at the point where I need to bring in the Line Item details. The amount of line items is depending on the order and always different. I need my XSLT to repeat until all line items are copied into the new XML file (I realize there's probably a better way to say that, but I'm not sure how to at the moment.)

XML:

<?xml version="1.0"?>

<cXML version="1.2.005" xml:lang="en-US" payloadID="xx" timestamp="2023-04-07T09:12:23">
  <Header>
    <From>
      <Credential domain="DUNS">
        <Identity>1282</Identity>
      </Credential>
      <Credential domain="CompanyName">
        <Identity>xxxx</Identity>
      </Credential>
      <Credential domain="InteropKey">
        <Identity>xxxx</Identity>
      </Credential>
    </From>
    <To>
      <Credential domain="CompanyName">
        <Identity>xxxx</Identity>
      </Credential>
    </To>
    <Sender>
      <Credential domain="DUNS">
        <Identity>xxxx</Identity>
        <SharedSecret></SharedSecret>
      </Credential>
      <UserAgent>WildFire</UserAgent>
    </Sender>
  </Header>
  <Request deploymentMode="production">
    <OrderRequest>
      <OrderRequestHeader orderID="3035" orderDate="2023-04-07T09:12:23" type="new">
        <Total>
          <Money currency="USD">42.40</Money>
        </Total>
        <BillTo><Address addressID="xxxx">
          <Name xml:lang="en-US">Billing</Name>
          <PostalAddress name="">
            <DeliverTo>xxxx</DeliverTo>
            <Street>xxxx</Street>
            <Street></Street>   
            <City>xxxx</City>
            <State>xx</State>
            <PostalCode>xxxxx</PostalCode>
            <Country isoCountryCode="US">US</Country>
          </PostalAddress>
          <Email>[email protected]</Email>
           <Phone>
            <TelephoneNumber>
              <CountryCode isoCountryCode="" />
              <AreaOrCityCode />
              <Number></Number>
            </TelephoneNumber>
          </Phone>
</Address></BillTo>
        <Shipping>
          <Money currency="USD">0</Money>
          <Description xml:lang="en-US">
          </Description>
        </Shipping>
        <Tax>
          <Money currency="USD">2.40</Money>
          <Description xml:lang="en-US">
          </Description>
        </Tax>
        
        <Comments xml:lang="en-US">TEST YO</Comments>
          <Extrinsic name="UserName">xxxx</Extrinsic><Extrinsic name="OrderFields"><Extrinsic name="Purchase Order Number:">5870010</Extrinsic></Extrinsic>
          <Extrinsic name="UserInteropID">xxxx</Extrinsic>
          <Extrinsic name="UserFirstName">xxxx</Extrinsic>
          <Extrinsic name="UserLastName">xxxx</Extrinsic>
          <Extrinsic name="UserPhone"></Extrinsic>
          <Extrinsic name="PaymentType">PurchaseOrder</Extrinsic><Extrinsic name="PaymentSource"><Extrinsic name="PaymentType">PurchaseOrder</Extrinsic><Extrinsic name="Amount">42.40</Extrinsic></Extrinsic>
          <Extrinsic name="BuyerInteropID">xxxx</Extrinsic>
          <Extrinsic name="BillingAddressInteropID"></Extrinsic>
          <Extrinsic name="OrderType">Standard</Extrinsic>
          
        <Extrinsic name="FromUserIP">xxxx</Extrinsic>
        
      </OrderRequestHeader>
      
    <ItemOut lineNumber="1" quantity="25" requestedDeliveryDate="">
        <ItemID>
          <SupplierPartID>243-7</SupplierPartID>
          <SupplierPartAuxiliaryID>243-7</SupplierPartAuxiliaryID>
        </ItemID>
        <ItemDetail>
          <UnitPrice>
            <Money currency="USD">1.600000</Money>
          </UnitPrice>
          <Description xml:lang="en-US">xxxx</Description>
          <UnitOfMeasure>EA</UnitOfMeasure>
          <Classification domain=""></Classification>
          
          
          <Extrinsic name="lineItemID">xxxx</Extrinsic>
          <Extrinsic name="productType">Static</Extrinsic>
          
          <Extrinsic name="quantityMultiplier">1</Extrinsic>
          <Extrinsic name="costCenter"></Extrinsic>
          <Extrinsic name="costCenterInteropID"></Extrinsic>    
          <Extrinsic name="ProductSpecs"><Extrinsic name="Trim Size">8.5"x11"</Extrinsic><Extrinsic name="Print Type">4/4</Extrinsic><Extrinsic name="Product Type">Static</Extrinsic><Extrinsic name="Stored Jobs\">xxxx</Extrinsic><Extrinsic name="File Name">xxxx</Extrinsic></Extrinsic>
          <Extrinsic name="productInteropID"></Extrinsic>
          <Extrinsic name="variantInteropID"></Extrinsic>
          <Extrinsic name="shippingAddressInteropID"></Extrinsic>
          
            
            
          <Extrinsic name="shipWeight"></Extrinsic>
          
        <Extrinsic name="UserName">xxxx</Extrinsic></ItemDetail>
        <ShipTo><Address addressID="xxxx">
          <Name xml:lang="en-US">Address 1</Name>
          <PostalAddress name="">
            <DeliverTo> </DeliverTo>
            <Street>xxxx</Street>
            <Street></Street>   
            <City>xxxx</City>
            <State>PA</State>
            <PostalCode>17601</PostalCode>
            <Country isoCountryCode="US">US</Country>
          </PostalAddress>
          <Email></Email>
           <Phone>
            <TelephoneNumber>
              <CountryCode isoCountryCode="" />
              <AreaOrCityCode />
              <Number>7173687374</Number>
            </TelephoneNumber>
          </Phone>
</Address></ShipTo>
          
          
        
      </ItemOut></OrderRequest>
  </Request>
</cXML>

My XSLT:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output indent="yes"/>
 <xsl:strip-space elements="*"/>

<xsl:template match="Header">
<Header>
    <From>
        <Credential domain="DUNS">
            <Identity>
                <xsl:value-of select="From/Credential[@domain='DUNS']/Identity" />
            </Identity>
        </Credential>
    </From>
    <To>
        <Credential domain="CompanyName">
            <Identity>
                <xsl:value-of select="To/Credential[@domain='CompanyName']/Identity" />
            </Identity>
        </Credential>
    </To>
    <Sender>
        <Credential domain="DUNS">
            <Identity>
                <xsl:value-of select="Sender/Credential[@domain='DUNS']/Identity" />
            </Identity>
            <SharedSecret /> <!--what does this map to??-->
        </Credential>
    </Sender>
</Header>
</xsl:template>

<xsl:template match="Request">
<Request deploymentMode="production">
    <OrderRequest>
        <OrderRequestHeader>
            <xsl:attribute name="orderID">
                <xsl:value-of select="OrderRequest/OrderRequestHeader/@orderID" />
            </xsl:attribute>
            <xsl:attribute name="orderDate">
                <xsl:value-of select="OrderRequest/OrderRequestHeader/@orderDate" />
            </xsl:attribute>
            <xsl:attribute name="type">
                <xsl:value-of select="OrderRequest/OrderRequestHeader/@type" />
            </xsl:attribute>
            
            <Total>
                <Money currency="USD">
                    <xsl:value-of select="OrderRequest/OrderRequestHeader/Total/Money" />
                </Money>
            </Total>
            
            <!-- not currently used by Printers Plan<BillTo>
                <Address addressID="NotCurrentlyUsed">
                    <Name xml:lang="en-US">
                        <xsl:value-of select="OrderRequest/OrderRequestHeader/BillTo/Address/Name" />
                    </Name>
                    <PostalAddress>
                        <xsl:attribute name="name">
                            <xsl:value-of select="OrderRequest/OrderRequestHeader/BillTo/PostalAddress/@name" />
                        </xsl:attribute>
                        <DeliverTo><xsl:value-of select="OrderRequest/OrderRequestHeader/BillTo/Address/PostalAddress/DeliverTo" /></DeliverTo>
                        <Street><xsl:value-of select="OrderRequest/OrderRequestHeader/BillTo/Address/PostalAddress/Street" /></Street>
                        <City><xsl:value-of select="OrderRequest/OrderRequestHeader/BillTo/Address/PostalAddress/City" /></City>
                        <State><xsl:value-of select="OrderRequest/OrderRequestHeader/BillTo/Address/PostalAddress/State" /></State>
                        <PostalCode><xsl:value-of select="OrderRequest/OrderRequestHeader/BillTo/Address/PostalAddress/PostalCode" /></PostalCode>
                        <Country>
                            <xsl:attribute name="isoCountryCode">
                                <xsl:value-of select="OrderRequest/OrderRequestHeader/BillTo/Address/PostalAddress/Country/@isoCountryCode" />
                            </xsl:attribute>
                            <xsl:value-of select="OrderRequest/OrderRequestHeader/BillTo/Address/PostalAddress/Country" />
                        </Country>
                    </PostalAddress>
                </Address>
            </BillTo>-->
            <!-- not currently used by Printers Plan<ShipTo>
                <Address addressID="NotCurrentlyUsed">
                    <Name xml:lang="en-US">
                        <xsl:value-of select="OrderRequest/OrderRequestHeader/ShipTo/Address/Name" />
                    </Name>
                    <PostalAddress>
                        <xsl:attribute name="name">
                            <xsl:value-of select="OrderRequest/OrderRequestHeader/ShipTo/PostalAddress/@name" />
                        </xsl:attribute>
                        <DeliverTo><xsl:value-of select="OrderRequest/OrderRequestHeader/ShipTo/Address/PostalAddress/DeliverTo" /></DeliverTo>
                        <Street><xsl:value-of select="OrderRequest/OrderRequestHeader/ShipTo/Address/PostalAddress/Street" /></Street>
                        <City><xsl:value-of select="OrderRequest/OrderRequestHeader/ShipTo/Address/PostalAddress/City" /></City>
                        <State><xsl:value-of select="OrderRequest/OrderRequestHeader/ShipTo/Address/PostalAddress/State" /></State>
                        <PostalCode><xsl:value-of select="OrderRequest/OrderRequestHeader/ShipTo/Address/PostalAddress/PostalCode" /></PostalCode>
                        <Country>
                            <xsl:attribute name="isoCountryCode">
                                <xsl:value-of select="OrderRequest/OrderRequestHeader/ShipTo/Address/PostalAddress/Country/@isoCountryCode" />
                            </xsl:attribute>
                            <xsl:value-of select="OrderRequest/OrderRequestHeader/ShipTo/Address/PostalAddress/Country" />
                        </Country>
                    </PostalAddress>
                </Address>
            </ShipTo>-->
            <Shipping>
                <Money currency="USD"><xsl:value-of select="OrderRequest/OrderRequestHeader/Shipping/Money" /></Money>
                <Description xml:lang="en-US"><xsl:value-of select="OrderRequest/OrderRequestHeader/Shipping/Description" /></Description>
            </Shipping>
            <Tax>
                <Money currency="USD"><xsl:value-of select="OrderRequest/OrderRequestHeader/Tax/Money" /></Money>
                <Description xml:lang="en-US"><xsl:value-of select="OrderRequest/OrderRequestHeader/Tax/Description" /></Description>
            </Tax>
            <Contact>
                <Name><xsl:value-of select="OrderRequest/OrderRequestHeader/Extrinsic[@name='UserFirstName']" />&#160;<xsl:value-of select="OrderRequest/OrderRequestHeader/Extrinsic[@name='UserLastName']" /></Name>
                <Email><xsl:value-of select="OrderRequest/OrderRequestHeader/Extrinsic[@name='UserName']" /></Email>
            </Contact>
            <Comments xml:lang="en-US">
                <xsl:value-of select="OrderRequest/OrderRequestHeader/Comments" />
            </Comments>
            <Extrinsic name="JobTitle">
            
            </Extrinsic>
            <Extrinsic name="Purchase Order Number:">
             <xsl:value-of select="OrderRequest/OrderRequestHeader/Extrinsic[@name='OrderFields']/Extrinsic[@name='Purchase Order Number:']" />
            </Extrinsic>
            <!--where do this go?<Extrinsic name="YourFieldName2">
            
            </Extinsic>-->
        </OrderRequestHeader>
        <!-- Item out is harder to figure out - needs to be a copy so it brings all items in... or repeat until all items are in-->
        <ItemOut>
            <xsl:attribute name="lineNumber"><xsl:value-of select="OrderRequest/ItemOut/@lineNumber" /></xsl:attribute>
            <xsl:attribute name="quantity"><xsl:value-of select="OrderRequest/ItemOut/@quantity" /></xsl:attribute>
            <xsl:attribute name="requestedDeliveryDate"><xsl:value-of select="OrderRequest/ItemOut/@requestedDeliveryDate" /></xsl:attribute>
            
            <ItemID>
                <SupplierPartID><xsl:value-of select="OrderRequest/ItemOut/ItemID/SupplierPartID" /></SupplierPartID>
            </ItemID>
        </ItemOut>
    </OrderRequest>
</Request>
</xsl:template>

 
</xsl:stylesheet>

I need it to repeat that to bring in all of the elements that may exist in the XML. Also - I realize this is likely far from the most efficient way to do this. I'm learning as I go. advice appreciated.

I did try to add a <xsl:template match="ItemOut"> around the element thinking that would reference all elements in the XML regardless of their attributes but it failed because it's inside of the other template tag. Maybe there's a better way to avoid the other template so I can use it here?

UPDATE (THIS MAY BE ALL YOU NEED TO READ NOW): xsl:for-each I think does what I need it to do as far as processing all of the elements in the XML. Now, I'm having trouble getting the attribute values. XML:

<ItemOut lineNumber="1" quantity="25" requestedDeliveryDate="">
        <ItemID>
          <SupplierPartID>243-7</SupplierPartID>
          <SupplierPartAuxiliaryID>243-7</SupplierPartAuxiliaryID>
        </ItemID>
</ItemOut>
<ItemOut lineNumber="2" quantity="75" requestedDeliveryDate="">
        <ItemID>
          <SupplierPartID>243-9</SupplierPartID>
          <SupplierPartAuxiliaryID>243-7</SupplierPartAuxiliaryID>
        </ItemID>
</ItemOut>

XSLT:

<xsl:for-each select="OrderRequest/ItemOut">
        <ItemOut>
            <xsl:attribute name="lineNumber"><xsl:value-of select="OrderRequest/ItemOut/@lineNumber" /></xsl:attribute>
            <xsl:attribute name="quantity"><xsl:value-of select="OrderRequest/ItemOut/@quantity" /></xsl:attribute>
            <xsl:attribute name="requestedDeliveryDate"><xsl:value-of select="OrderRequest/ItemOut/@requestedDeliveryDate" /></xsl:attribute>
            
            <ItemID>
                <SupplierPartID><xsl:value-of select="OrderRequest/ItemOut/ItemID/SupplierPartID" /></SupplierPartID>
            </ItemID>
        </ItemOut>
        </xsl:for-each>

Intended Result:

<ItemOut lineNumber="1" quantity="25" requestedDeliveryDate="">
        <ItemID>
          <SupplierPartID>243-7</SupplierPartID>
          <SupplierPartAuxiliaryID>243-7</SupplierPartAuxiliaryID>
        </ItemID>
</ItemOut>
<ItemOut lineNumber="2" quantity="75" requestedDeliveryDate="">
        <ItemID>
          <SupplierPartID>243-9</SupplierPartID>
          <SupplierPartAuxiliaryID>243-7</SupplierPartAuxiliaryID>
        </ItemID>
</ItemOut>

I realize this result is exactly like the input... there are other elements I need to change that aren't showing here currently as I'm just looking for proof of concept at this point. Right now it's outputting both elements with the attributes, but the attribute values are empty.


Solution

  • The instruction:

    <xsl:for-each select="OrderRequest/ItemOut">
    

    puts you in the context of ItemOut. From there, the path to the current element's attributes is just @attribute-name. Likewise for the descendant element SupplierPartID.

    Try:

    <xsl:for-each select="OrderRequest/ItemOut">
        <ItemOut>
            <xsl:attribute name="lineNumber">
                <xsl:value-of select="@lineNumber" />
            </xsl:attribute>
            <xsl:attribute name="quantity">
                <xsl:value-of select="@quantity" />
            </xsl:attribute>
            <xsl:attribute name="requestedDeliveryDate">
                <xsl:value-of select="@requestedDeliveryDate" />
            </xsl:attribute>
            <ItemID>
                <SupplierPartID>
                    <xsl:value-of select="ItemID/SupplierPartID" />
                </SupplierPartID>
            </ItemID>
        </ItemOut>
    </xsl:for-each>
    

    Or, if you prefer, take advantage of attribute value templates to shorten your code:

    <xsl:for-each select="OrderRequest/ItemOut">
        <ItemOut lineNumber="{@lineNumber}" quantity="{@quantity}" requestedDeliveryDate="{@requestedDeliveryDate}">
            <ItemID>
                <SupplierPartID>
                    <xsl:value-of select="ItemID/SupplierPartID" />
                </SupplierPartID>
            </ItemID>
        </ItemOut>
    </xsl:for-each>