Stephan
Stephan

Reputation: 527

combining xml nodes through xslt

I have the following xml code (simplified, the pseudonyms is always present, but Patient could als be an other tag with different content):

    <Record>
    <Pseudonyms>
        <Pseudonym type="B">e113</Pseudonym>
        <Pseudonym type="M">lss9</Pseudonym>
        <Pseudonym type="S">f6rr</Pseudonym>
    </Pseudonyms>
    <Data>
        <patient>
            <Sexe>V</Sexe>
            <yob>1984</yob>
            <status>2</status>
        </patient>
    </Data>
    </Record>

I'm looking for an xslt solution so the result looks like:

    <Record>
        <patient>
            <B>e113</B>
            <M>lss9</M>
            <S>f6rr</S>
            <Sexe>V</Sexe>
            <yob>1984</yob>
            <status>2</status>
        </patient>  
       </Record>

The question would be how I can combine the pseudonyms tag and, in this case the Patient-tag to one record. As mentioned this is a simplified example. The Patient-tag can also be an other tag in the same document. So I need a generic solution that combines whatever tags comes after with the Pseudonyms-tag with the prefix of the tag that follows after (in this example Patient).

I know how to transform <Pseudonym type="B">e113</Pseudonym> into <B>e113</B>, but I don't know how to combine the tags in the right way to come to the result in the example.

I hope I could explained well enough what I try to accomplice. Tia.

Edit:

What I didn't mention is that Patient is just one of the many different tags. So a short version would be:

XML

    <?xml version="1.0" encoding="UTF-8"?>
    <Bestand>
    <Record>
    <Pseudonyms>
        <Pseudonym type="B">e113</Pseudonym>
        <Pseudonym type="M">lss9</Pseudonym>
        <Pseudonym type="S">f6rr</Pseudonym>
    </Pseudonyms>
    <Data>
        <Patient>
            <Sexe>V</Sexe>
            <yob>1984</yob>
            <status>2</status>
        </Patient>
    </Data>
    </Record>

    <Record>
    <Pseudonyms>
        <Pseudonym type="B">e113</Pseudonym>
        <Pseudonym type="M">lss9</Pseudonym>
        <Pseudonym type="S">f6rr</Pseudonym>
    </Pseudonyms>
    <Data>
        <SurveyA>
            <Item01>1</Item01>
            <Item02>4</Item02>
            <Item03>8</Item03>
        </SurveyA>
    </Data>
    </Record>
    <Record>
    <Pseudonyms>
        <Pseudonym type="B">e113</Pseudonym>
        <Pseudonym type="M">lss9</Pseudonym>
        <Pseudonym type="S">f6rr</Pseudonym>
    </Pseudonyms>
    <Data>
        <SurveyB>
            <Item01>1</Item01>
            <Item02>3</Item02>
            <Item03>2</Item03>
        </SurveyB>
    </Data>
    </Record>

   </Bestand>

My xsl looks like:

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="/">
    <xsl:element name="message">            
         <xsl:apply-templates select="//Data"/>                  
    </xsl:element>
    </xsl:template>

    <xsl:template match="Patient|SurveyA|SurveyB">          
        <xsl:element name="{name()}">           
            <xsl:element name="B"><xsl:value-of select="//Pseudonyms/Pseudonym[@type='B']" /></xsl:element>
            <xsl:element name="M"><xsl:value-of select="//Pseudonyms/Pseudonym[@type='M']" /></xsl:element>
            <xsl:element name="S"><xsl:value-of select="//Pseudonyms/Pseudonym[@type='S']" /></xsl:element>
            <xsl:copy-of select="node()"/>
        </xsl:element>      
    </xsl:template>     
</xsl:stylesheet>

This results in the next output which is what I want, but I also wonder is there would be a better, more cleaner way.

![Output][1]
[1]: https://i.sstatic.net/1Lcea.png

As said, this works, I have just one problem left, there could be multiple Patient, SurveyA en SurveyB records. When I apply my stylesheet, the whole thing is not sorted. So there can be a Patient record followed by a SurveyA record. This SurveyA record can, in its turn be followed by a Patient record. It would be nice if I could group the specific items. This I haven't been able to accomplice.

Upvotes: 0

Views: 73

Answers (2)

Daniel Haley
Daniel Haley

Reputation: 52848

Here's an option that similar to forty-two's answer, but takes into account your updated question.

Notes:

  • I'm also sorting the Pseudonym by @type. If this isn't necessary, just remove the xsl:sort.
  • I'm using a separate template to handle the transform of the Pseudonym elements. If they are as static as the appear in your question, you can go back to hard coding the elements in and using xsl:value-of. (You don't need to use xsl:element though.

XML Input (modified slightly to show sorting)

<Bestand>
    <Record>
        <Pseudonyms>
            <Pseudonym type="B">e113a</Pseudonym>
            <Pseudonym type="M">lss9a</Pseudonym>
            <Pseudonym type="S">f6rra</Pseudonym>
        </Pseudonyms>
        <Data>
            <Patient>
                <Sexe>V</Sexe>
                <yob>1984</yob>
                <status>2</status>
            </Patient>
        </Data>
    </Record>

    <Record>
        <Pseudonyms>
            <Pseudonym type="B">e113</Pseudonym>
            <Pseudonym type="M">lss9</Pseudonym>
            <Pseudonym type="S">f6rr</Pseudonym>
        </Pseudonyms>
        <Data>
            <SurveyA>
                <Item01>1</Item01>
                <Item02>4</Item02>
                <Item03>8</Item03>
            </SurveyA>
        </Data>
    </Record>

    <Record>
        <Pseudonyms>
            <Pseudonym type="M">lss9</Pseudonym>
            <Pseudonym type="B">e113</Pseudonym>
            <Pseudonym type="S">f6rr</Pseudonym>
        </Pseudonyms>
        <Data>
            <Patient>
                <Sexe>W</Sexe>
                <yob>1985</yob>
                <status>3</status>
            </Patient>
        </Data>
    </Record>


    <Record>
        <Pseudonyms>
            <Pseudonym type="B">e113</Pseudonym>
            <Pseudonym type="M">lss9</Pseudonym>
            <Pseudonym type="S">f6rr</Pseudonym>
        </Pseudonyms>
        <Data>
            <SurveyB>
                <Item01>1</Item01>
                <Item02>3</Item02>
                <Item03>2</Item03>
            </SurveyB>
        </Data>
    </Record>

</Bestand>

XSLT 1.0

<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="/*">
        <message>
            <xsl:apply-templates select="Record/Data/*">
                <xsl:sort select="local-name()"/>
            </xsl:apply-templates>
        </message>          
    </xsl:template>

    <xsl:template match="Data/*">          
        <xsl:copy>
            <xsl:apply-templates select="../../Pseudonyms/Pseudonym">
                <xsl:sort select="@type"/>
            </xsl:apply-templates>
            <xsl:copy-of select="node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="Pseudonym">
        <xsl:element name="{@type}">
            <xsl:value-of select="."/>
        </xsl:element>
    </xsl:template>

</xsl:stylesheet>

XML Output

<message>
   <Patient>
      <B>e113a</B>
      <M>lss9a</M>
      <S>f6rra</S>
      <Sexe>V</Sexe>
      <yob>1984</yob>
      <status>2</status>
   </Patient>
   <Patient>
      <B>e113</B>
      <M>lss9</M>
      <S>f6rr</S>
      <Sexe>W</Sexe>
      <yob>1985</yob>
      <status>3</status>
   </Patient>
   <SurveyA>
      <B>e113</B>
      <M>lss9</M>
      <S>f6rr</S>
      <Item01>1</Item01>
      <Item02>4</Item02>
      <Item03>8</Item03>
   </SurveyA>
   <SurveyB>
      <B>e113</B>
      <M>lss9</M>
      <S>f6rr</S>
      <Item01>1</Item01>
      <Item02>3</Item02>
      <Item03>2</Item03>
   </SurveyB>
</message>

Upvotes: 1

forty-two
forty-two

Reputation: 12817

Here's one way of doing it:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/Record">
    <Record>
        <patient>
            <xsl:apply-templates select="Pseudonyms/Pseudonym" />
            <xsl:apply-templates select="Data/patient/*" />
        </patient>
    </Record>
</xsl:template>

<xsl:template match="Pseudonym">
    <xsl:element name="{@type}">
        <xsl:value-of select="." />
    </xsl:element>
</xsl:template>

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

</xsl:stylesheet>

Upvotes: 1

Related Questions