1

I'm new to XML/XSLT.

I need to transform an xml file containing elements like this:

<person name="John Smith" />
<person name="Mary Ann Smith" />

Into something like this:

<person firstname="John" lastname="Smith" />
<person firstname="Mary Ann" lastname="Smith" />

name attribute can contain a variable number of words, only the last one should go to lastname and the rest goes to firstname.

I'm trying to achieve this using XSLT and xsltproc on linux, but any other simple solution is welcome.

Thanks, Bruno

1 Answer 1

1

XSLT 1.0 (and thus XPath 1.0) is somewhat limited in its string manipulation functions. There are the substring-before and substring-after functions which allows you to extract the substring of a given string preceding and following the first occurrence of a particular pattern, but there's no direct way to split a string at the last occurrence. You'll have to use a (tail-)recursive template

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:template match="@*|node()">
    <xsl:copy><xsl:apply-templates select="@*|node()" /></xsl:copy>
  </xsl:template>

  <xsl:template match="person/@name">
    <xsl:call-template name="splitName"/>
  </xsl:template>

  <xsl:template name="splitName">
    <!-- start with nothing in $first and all the words in $rest -->
    <xsl:param name="first" select="''" />
    <xsl:param name="rest" select="." />

    <xsl:choose>
      <!-- if rest contains more than one word -->
      <xsl:when test="substring-after($rest, ' ')">
        <xsl:call-template name="splitName">
          <!-- move the first word of $rest to the end of $first and recurse.
               For the very first word this will add a stray leading space
               to $first, which we will clean up later. -->
          <xsl:with-param name="first" select="concat($first, ' ',
                                               substring-before($rest, ' '))" />
          <xsl:with-param name="rest" select="substring-after($rest, ' ')" />
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <!-- $rest is now just the last word of the original name, and $first
             contains all the others plus a leading space that we have to
             remove -->
        <xsl:attribute name="firstname">
          <xsl:value-of select="substring($first, 2)" />
        </xsl:attribute>
        <xsl:attribute name="lastname">
          <xsl:value-of select="$rest" />
        </xsl:attribute>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

Example:

$ cat names.xml
<people>
  <person name="John Smith" />
  <person name="Mary Ann Smith" />
</people>
$ xsltproc split-names.xsl names.xml
<?xml version="1.0"?>
<people>
  <person firstname="John" lastname="Smith"/>
  <person firstname="Mary Ann" lastname="Smith"/>
</people>

If you don't want the <?xml...?> line then add

<xsl:output method="xml" omit-xml-declaration="yes" />

to the top of the stylesheet, immediately following the opening <xsl:stylesheet> tag.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.