Josh McKearin
Josh McKearin

Reputation: 742

Concatenate two node values with a comma

I need to transform the XML and am having some issues...

Current XML:

<?xml version="1.0" encoding="utf-8"?>
 <Employees>
  <Employee>
   <ManagerFirstName>Joe</ManagerFirstName>
   <ManagerLastName>Schmoe</ManagerLastName>
  </Employee>
 </Employees>

Desired Output:

<?xml version="1.0" encoding="utf-8"?>
 <Employees>
  <Employee>
   <supervisorName>Schmoe, Joe</supervisorName>
  </Employee>
 </Employees>

Current XSL:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" >
  <xsl:template match="/">
    <xsl:apply-templates select="*"/>
  </xsl:template>
  <xsl:template match="node()">
    <xsl:copy><xsl:apply-templates select="node()"/></xsl:copy>
  </xsl:template>
  <xsl:template match="ManagerFirstName">
        <supervisorName>
        <xsl:apply-templates select="node()"/>
        <xsl:value-of  select="/ManagerLastName"/>
        <xsl:text>, </xsl:text>
        <xsl:value-of select="/ManagerFirstName"/>
        </supervisorName>
  </xsl:template>
</xsl:stylesheet>

This is not working and I can not figure it out. The XML it is outputting at the moment looks like this:

<?xml version="1.0" encoding="utf-8"?>
 <Employees>
  <Employee>
   <supervisorName>Joe, </supervisorName>
   <ManagerLastName>Schmoe/ManagerLastName>
  </Employee>
 </Employees>

I feel like I'm so close...

UPDATE How would I go about making sure that if ManagerFirstName and ManagerLastName are blank, that supervisorName does not have the comma inside of it?

UPDATE 2

<?xml version="1.0" encoding="UTF-8" ?>
 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output indent="yes"/> <xsl:strip-space elements="*"/>
 <xsl:template match="/">
   <xsl:apply-templates select="*"/>
 </xsl:template>
 <xsl:template match="@*|node()">
   <xsl:copy>
     <xsl:apply-templates select="@*|node()"/>
   </xsl:copy>
 </xsl:template>
 <xsl:template match="Employee">
   <tbl_EmployeeList><xsl:apply-templates select="@*|node()"/></tbl_EmployeeList>
 </xsl:template>
 <xsl:template match="tbl_EmployeeList">
   <xsl:copy>
     <xsl:apply-templates select="@*|node()"/>
       <supervisorName>
         <xsl:value-of select="(ManagerLastName,ManagerFirstName)" separator=", "/>
       </supervisorName>
   </xsl:copy>
 </xsl:template>
</xsl:stylesheet>

Upvotes: 4

Views: 9282

Answers (5)

Carles Sala
Carles Sala

Reputation: 2109

All responses seem good, but I think that this approach is cleaner (Just replace your ManagerFirstName template by this one):

<xsl:template match="Employee">
    <supervisorName>
        <xsl:value-of select="concat(ManagerLastName,', ',ManagerFirstName)"/>
    </supervisorName>
</xsl:template>

UPDATE:

If you want to make the comma appear only if both nodes exist and are not empty you can use an if with string-length for that:

<xsl:template match="Employee">
    <supervisorName>
        <xsl:value-of select="ManagerLastName"/>
        <xsl:if test="string-length(ManagerLastName) and string-length(ManagerFirstName)">
            <xsl:text> ,</xsl:text>
        </xsl:if>
        <xsl:value-of select="ManagerFirstName"/>
    </supervisorName>
</xsl:template>

UPDATE 2: Yet a cleaner solution following xslt 2.0 approach, but which also covers the case of an empty node.

<xsl:template match="Employee">
    <supervisorName>
        <xsl:value-of select="(ManagerLastName[text()],ManagerFirstName[text()])" separator=", "/>
    </supervisorName>
</xsl:template>

Upvotes: 1

Daniel Haley
Daniel Haley

Reputation: 52858

Since you're using XSLT 2.0 you can use the separator attribute in xsl:value-of...

XML Input

<Employees>
    <Employee>
        <ManagerFirstName>Joe</ManagerFirstName>
        <ManagerLastName>Schmoe</ManagerLastName>
    </Employee>
</Employees>

XSLT 2.0

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

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

    <xsl:template match="Employee">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <supervisorName>
                <xsl:value-of select="(ManagerLastName,ManagerFirstName)" separator=", "/>
            </supervisorName>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

XML Output

<Employees>
   <Employee>
      <supervisorName>Schmoe, Joe</supervisorName>
   </Employee>
</Employees>

Note: If there is no ManagerLastName or ManagerFirstName, no separator will be output.

Upvotes: 5

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243449

Here is a fully "push style" solution:

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

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

 <xsl:template match="Employee">
  <supervisorName><xsl:apply-templates/></supervisorName>
 </xsl:template>

 <xsl:template match="Employee/*">
  <xsl:value-of select="concat(', ', .)"/>
 </xsl:template>
 <xsl:template match="Employee/*[1]"><xsl:apply-templates/></xsl:template>
</xsl:stylesheet>

Upvotes: 2

Ian Roberts
Ian Roberts

Reputation: 122364

You are close. In the ManagerFirstName template you need a slightly different XPath to pull out the last name:

<xsl:template match="ManagerFirstName">
    <supervisorName>
        <xsl:value-of select="../ManagerLastName"/>
        <xsl:text>, </xsl:text>
        <xsl:apply-templates select="node()"/>
    </supervisorName>
</xsl:template>

(the apply-templates is sufficient to give you the value of the ManagerFirstName, you don't need a specific value-of for that). You then need a no-op template to stop it copying the last name independently

<xsl:template match="ManagerLastName" />

Also note that the usual identity template would match and apply-templates to @*|node() rather than just node() - it makes no difference in your example document because you're not using any attributes, but if your original XML had attributes then your version of the identity template would drop them.

Upvotes: 2

Lukasz
Lukasz

Reputation: 7662

You can do this in the following way:

<?xml version="1.0" encoding="UTF-8"?>
<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="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="Employee">
        <Employee>
            <supervisorName><xsl:value-of select="concat(ManagerLastName, ', ', ManagerFirstName)"/></supervisorName>
        </Employee>
    </xsl:template>
</xsl:stylesheet>

Upvotes: 3

Related Questions