0
votes

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>
1
This is similar to: stackoverflow.com/questions/33448325/… -- P.S. Please provide a usable example. 1 is not a valid element name.michael.hor257k
The 1s don't matter, I just used them as a place holder. I'll see if the linked question works for me.RJohnson
It matters to any of us who would want to use your code for testing in order to provide you with an answer. Please save us the time.michael.hor257k
I updated the XML. The linked question uses for-each-group which seems to work if the nodes all had the same child nodes, but they won't, and I won't know what nodes are between the ones that I need to separate by.RJohnson
Try <xsl:for-each-group select="*" group-starting-with="UserName">.michael.hor257k

1 Answers

2
votes

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