RJohnson
RJohnson

Reputation: 188

XSLT 1.0 All Nodes between two different nodes

I have a list of nodes in XML that I need to turn into some sort of structured data, and I'm having some trouble.

I've got a good idea of how to get the child data under the headers (Customer,User) but I'm really struggling with how to select between two differently named nodes, including those nodes. I'm really looking for the XPath which will allow for the selection of those areas.

So for the Customer node, I need to select between CustomerName through the node before UserName (There are many more nodes in the real data). Then for each user (of which there is an unknown amount) I need to get between UserName and the node before the next UserName, again with many more than just two nodes in the real data.

I've tried to use a combination of preceding-sibling, following-sibling, and count, but I just can't get the right combination of things to work. Any pointers in the right direction will be a great help.

I'd prefer XSLT 1.0, but 2.0 is available for me to use.

Example Data:

<Data>
  <CustomerName>
    <Name>ABCCompany</Name>
  </CustomerName>
  <CustomerAddress>
    <City>AnyCity</City>
    <State>AnyState</State>
  </CustomerAddress>
  <UserName>
    <Name>Betty<Name>
  </UserName>
  <UserAddress>
    <City>AnyCity</City>
    <State>AnyState</State>
  </UserAddress>
  <UserName>
    <Name>Johnny</Name>
  </UserName>
  <UserAddress>
    <City>AnyCity</City>
    <State>AnyState</State>
  </UserAddress>
</Data>

Desired Output:

<Data>
  <Customer>
    <CustomerName>
      <Name>ABCCompany</Name>
    </CustomerName>
    <CustomerAddress>
    <City>AnyCity</City>
    <State>AnyState</State>
    </CustomerAddress>
  </Customer>
  <Users>
    <User>
      <UserName>
        <Name>Betty</Name>
      </UserName>
      <UserAddress>
    <City>AnyCity</City>
    <State>AnyState</State>
      </UserAddress>
    </User>
    <User>
      <UserName>
        <Name>Johnny</Name>
      </UserName>
      <UserAddress>
    <City>AnyCity</City>
    <State>AnyState</State>
      </UserAddress>
    </User>
  </Users>
</Data>

Upvotes: 0

Views: 404

Answers (1)

michael.hor257k
michael.hor257k

Reputation: 116959

Here's a rough prototype of how you could approach this in XSLT 2.0:

<xsl:stylesheet version="2.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/Data">
    <xsl:copy>
        <xsl:for-each-group select="*" group-starting-with="CustomerName|UserName">
            <group type="{name(current-group()[1])}">
                <xsl:copy-of select="current-group()" />
            </group>
        </xsl:for-each-group>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

Applied to your example input (corrected for well-formedness!), the result will be:

<?xml version="1.0" encoding="UTF-8"?>
<Data>
   <group type="CustomerName">
      <CustomerName>
         <Name>ABCCompany</Name>
      </CustomerName>
      <CustomerAddress>
         <City>AnyCity</City>
         <State>AnyState</State>
      </CustomerAddress>
   </group>
   <group type="UserName">
      <UserName>
         <Name>Betty</Name>
      </UserName>
      <UserAddress>
         <City>AnyCity</City>
         <State>AnyState</State>
      </UserAddress>
   </group>
   <group type="UserName">
      <UserName>
         <Name>Johnny</Name>
      </UserName>
      <UserAddress>
         <City>AnyCity</City>
         <State>AnyState</State>
      </UserAddress>
   </group>
</Data>

You can use the expression name(current-group()[1]) to choose the appropriate element wrapper for each group.


Demo: http://xsltransform.hikmatu.com/bdxtpC

Upvotes: 2

Related Questions