简体   繁体   中英

How do I use XSLT to merge XML elements and then structure elements/attributes of the same name/type?

Main issue

I have an XML file which has multiple similar elements with slightly different information. So, lets say I have two elements with the same X attirbute, but different Y attributes. Here is an example:

<FILE>
  <ELEMENT>
    <ATTRIBUTEX>1</ATTRIBUTEX>
    <ATTRIBUTEY>A</ATTRIBUTEY>
  </ELEMENT>
  <ELEMENT>
    <ATTRIBUTEX>1</ATTRIBUTEX>
    <ATTRIBUTEY>B</ATTRIBUTEY>
  </ELEMENT>
</FILE>

What I want to do is--based on the fact that both elements have the same X attribute--merge them into a single element with one attribute X and multiple child elements (of the same type)--each of which contain each of the different attributes Y. So, for example, I want my file to end up like this:

<FILE>
  <ELEMENT>
    <ATTRIBUTEX>1</ATTRIBUTEX>
    <NEWELEMENT>
      <ATTRIBUTEY>A</ATTRIBUTEY>
    </NEWELEMENT>
    <NEWELEMENT>
      <ATTRIBUTEY>B</ATTRIBUTEY>
    </NEWELEMENT>
  </ELEMENT>
</FILE>

Possible solution?

One possible solution I can think of, but lack the knowledge to execute, is made up of two steps:

  1. Firstly, I could merge all elements which contain the X attribute so that the information is, at the least, in one place. I don't know quite how to do this.
  2. Secdonly, once this is done, this opens me up--in theory--to use my knowledge of XSLT to re-structure the element.

Problem with the possible solution

However, once all elements that containin the X attribute are merged together ( first step ), I will have a file in which there will be attributes with the same name (specifically, the Y attribute). Here is an example:

<FILE>
  <ELEMENT>
    <ATTRIBUTEX>1</ATTRIBUTEX>
    <ATTRIBUTEY>A</ATTRIBUTEY>
    <ATTRIBUTEY>B</ATTRIBUTEY>
  </ELEMENT>
</FILE>

What this means is that--at least with my knowledge--when I execute an XSLT file on the above XML ( second step ), it cannot distinguish between the two Y elements when sorting them into the two new child elements ( NEWELEMENT ).

So, let's say I execute the following XSLT on the above XML:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="FILE">
  <Record>
    <xsl:for-each select = "ELEMENT">
      <ELEMENT>
        <xsl:copy-of select="ATTRIBUTEX"/>
      
        <NEWELEMENT>
          <xsl:copy-of select="ATTRIBUTEY"/>
        </NEWELEMENT>
        
        <NEWELEMENT>
          <xsl:copy-of select="ATTRIBUTEY"/>
        </NEWELEMENT>

      </ELEMENT>
    </xsl:for-each> 
  </Record>
</xsl:template>
</xsl:stylesheet>

The output is this:

<Record>
    <ELEMENT>
        <ATTRIBUTEX>1</ATTRIBUTEX>
        <NEWELEMENT>
            <ATTRIBUTEY>A</ATTRIBUTEY>
            <ATTRIBUTEY>B</ATTRIBUTEY>
        </NEWELEMENT>
        <NEWELEMENT>
            <ATTRIBUTEY>A</ATTRIBUTEY>
            <ATTRIBUTEY>B</ATTRIBUTEY>
        </NEWELEMENT>
    </ELEMENT>
</Record>

As you can see, the XSLT has taken all and any ATTRIBUTEY elements and put them into each NEWELEMENT rather than distinguishing between them and placing one ATTRIBUTEY in the first NEWELEMENT , and the second ATTRIBUTEY in the second NEWELEMENT . I am in need of an XSLT file which does just that, and, as stated further up, produces an XML that looks like this:

<FILE>
  <ELEMENT>
    <ATTRIBUTEX>1</ATTRIBUTEX>
    <NEWELEMENT>
      <ATTRIBUTEY>A</ATTRIBUTEY>
    </NEWELEMENT>
    <NEWELEMENT>
      <ATTRIBUTEY>B</ATTRIBUTEY>
    </NEWELEMENT>
  </ELEMENT>
</FILE>

Would anyone be able to help me with this? Any solutions which generate the desired output (above) from the very first example file would be much appreciated. It's a bonus if that solution follows steps one and two. Thanks!

I'm getting your requested output from the XSLT 3.0 below.

Just a couple of notes beforehand:

  1. You mention ATTRIBUTEs but, in fact, everything is an element in your XML document. There are no attributes in the XML sense.
  2. I really don't see the need to create the additional subelements.. you can already have a list of elements of the same name.

Nonetheless, as an exercise in style(sheets), here is a stylesheet that produces your output from your given input:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="#all">

  <xsl:mode on-no-match="shallow-copy"/>
  
  <xsl:output method="xml" indent="yes" />

  <xsl:template match="/FILE" >
    <FILE>
      <xsl:for-each-group select="*" group-by="name(.)" >
        <xsl:variable name="parentName" as="xs:string" select="current-grouping-key()" /> 
        
        <xsl:variable name="childNamesSameValues" as="xs:string*" >
          <xsl:for-each-group select="current-group()/*" group-by="name(.)" >
            <xsl:if test="count(distinct-values(current-group()/text())) eq 1">
              <xsl:sequence select="current-grouping-key()" />
            </xsl:if>
          </xsl:for-each-group>
        </xsl:variable>
            
        <xsl:element name="{$parentName}" >
          <xsl:for-each-group select="current-group()/*" group-by="name(.)" >
            <xsl:choose>
              <xsl:when test="current-grouping-key() = $childNamesSameValues">
                <xsl:copy-of select="current-group()[1]" />
              </xsl:when>
              <xsl:otherwise >
                <xsl:for-each select="current-group()" >
                  <xsl:element name="NEW{$parentName}" >
                    <xsl:copy-of select="." />
                  </xsl:element>
                </xsl:for-each>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:for-each-group>
        </xsl:element>
      </xsl:for-each-group>
    </FILE>
  </xsl:template>
  
</xsl:stylesheet>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM