Extracting the first two initials of a person's name I've come up with a hackish approach to get my XSL function to only return the first 2 initials of a person. I think there should be a more efficient way to do this though. The replace specifically is what I think I should be able to get rid of, looking through docs though I can't figure out how to correctly iterate with the split.

So 5 examples of my XML:

&lt;name&gt;
    &lt;surname&gt;Test I&lt;/surname&gt;
    &lt;given-names&gt;Chris&lt;/given-names&gt;
&lt;/name&gt;
&lt;name&gt;
    &lt;surname&gt;Test II&lt;/surname&gt;
    &lt;given-names&gt;Chris Chris&lt;/given-names&gt;
&lt;/name&gt;
&lt;name&gt;
    &lt;surname&gt;Test III&lt;/surname&gt;
    &lt;given-names&gt;Chris Eat Meat&lt;/given-names&gt;
&lt;/name&gt;
&lt;name&gt;
    &lt;surname&gt;Test IV&lt;/surname&gt;
    &lt;given-names&gt;Chris-Eat Meat&lt;/given-names&gt;
&lt;/name&gt;
&lt;name&gt;
    &lt;surname&gt;Test V&lt;/surname&gt;
    &lt;given-names&gt;Chris-Eat-Meat&lt;/given-names&gt;
&lt;/name&gt;


My function call:

&lt;xsl:value-of select="pull_it:get_initials(name/given-names/text())"/&gt; &lt;!-- name/given-names --&gt;


and my inefficient XSL function:

&lt;xsl:function name="pull_it:get_initials"&gt;
    &lt;xsl:param name="names"/&gt;
    &lt;xsl:value-of select="
            replace(string-join(
                for $token in tokenize($names,'(-|\s+)')[string-length(.) &gt; 0]
                    return concat(substring($token,1,1),'.'),
                ''), '(\w\.\w?\.?).*', '$1')
                    "/&gt;
&lt;/xsl:function&gt;


So with the first two XML blocks the expected output/return is:

C.


and 

C. C.


The remaining three should return (as expected, no M. because we only want the first 2 initials)

C. E.


but I thought there would be a way to stop the iteration. For example my PHP just has:

$abbreviated_firstnames = array();
$firstnames = mb_split('(\s+|-)', html_entity_decode($this-&gt;firstname,ENT_QUOTES,'UTF-8'));
$intial_count = 0;
foreach ($firstnames as $firstname) {
    $intial_count++;
    $firstinit = mb_substr($firstname,0,1,'UTF-8');
    if ($firstinit) {
        $abbreviated_firstnames[] = $firstinit . '.';
        if($intial_count &gt;= 2) {
            break; // &lt;---- we got 2 matches stop NOW
        }
    }
}
return implode(' ',$abbreviated_firstnames);
I think you should be able to get rid of the replace() by testing the position of the token in an additional predicate. 

You should also be able to remove the string-join() (unless you don't want spaces between the initials).

You can also remove the string-length() test by adjusting the regex for -.

New function based on above suggestions:

&lt;xsl:function name="pull_it:get_initials"&gt;
  &lt;xsl:param name="names"/&gt;
  &lt;xsl:value-of select="
    for $token in tokenize($names,'(-+|\s+)')[2 &gt;= position()]
    return concat(substring($token,1,1),'.')
    "/&gt;
&lt;/xsl:function&gt;


Complete example (used Saxon-HE 9.5 to test)...

XML Input (Added test VI)

&lt;doc&gt;
    &lt;name&gt;
        &lt;surname&gt;Test I&lt;/surname&gt;
        &lt;given-names&gt;Chris&lt;/given-names&gt;
    &lt;/name&gt;
    &lt;name&gt;
        &lt;surname&gt;Test II&lt;/surname&gt;
        &lt;given-names&gt;Chris Chris&lt;/given-names&gt;
    &lt;/name&gt;
    &lt;name&gt;
        &lt;surname&gt;Test III&lt;/surname&gt;
        &lt;given-names&gt;Chris Eat Meat&lt;/given-names&gt;
    &lt;/name&gt;
    &lt;name&gt;
        &lt;surname&gt;Test IV&lt;/surname&gt;
        &lt;given-names&gt;Chris-Eat Meat&lt;/given-names&gt;
    &lt;/name&gt;
    &lt;name&gt;
        &lt;surname&gt;Test V&lt;/surname&gt;
        &lt;given-names&gt;Chris-Eat-Meat&lt;/given-names&gt;
    &lt;/name&gt;
    &lt;name&gt;
        &lt;surname&gt;Test VI&lt;/surname&gt;
        &lt;given-names&gt;Chris---Eat----Meat&lt;/given-names&gt;
    &lt;/name&gt;
&lt;/doc&gt;


XSLT 2.0

&lt;xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:pull_it="pull_it"
  exclude-result-prefixes="pull_it"&gt;
  &lt;xsl:output indent="yes"/&gt;
  &lt;xsl:strip-space elements="*"/&gt;

  &lt;xsl:function name="pull_it:get_initials"&gt;
    &lt;xsl:param name="names"/&gt;
    &lt;xsl:value-of select="
      for $token in tokenize($names,'(-+|\s+)')[2 &gt;= position()]
      return concat(substring($token,1,1),'.')
      "/&gt;
  &lt;/xsl:function&gt;

  &lt;xsl:template match="@*|node()"&gt;
    &lt;xsl:copy&gt;
      &lt;xsl:apply-templates select="@*|node()"/&gt;
    &lt;/xsl:copy&gt;
  &lt;/xsl:template&gt;

  &lt;xsl:template match="name"&gt;
    &lt;xsl:copy&gt;
      &lt;xsl:apply-templates select="@*|node()"/&gt;
      &lt;initials&gt;&lt;xsl:value-of select="pull_it:get_initials(given-names)"/&gt;&lt;/initials&gt;
    &lt;/xsl:copy&gt;
  &lt;/xsl:template&gt;

&lt;/xsl:stylesheet&gt;


XML Output

&lt;doc&gt;
   &lt;name&gt;
      &lt;surname&gt;Test I&lt;/surname&gt;
      &lt;given-names&gt;Chris&lt;/given-names&gt;
      &lt;initials&gt;C.&lt;/initials&gt;
   &lt;/name&gt;
   &lt;name&gt;
      &lt;surname&gt;Test II&lt;/surname&gt;
      &lt;given-names&gt;Chris Chris&lt;/given-names&gt;
      &lt;initials&gt;C. C.&lt;/initials&gt;
   &lt;/name&gt;
   &lt;name&gt;
      &lt;surname&gt;Test III&lt;/surname&gt;
      &lt;given-names&gt;Chris Eat Meat&lt;/given-names&gt;
      &lt;initials&gt;C. E.&lt;/initials&gt;
   &lt;/name&gt;
   &lt;name&gt;
      &lt;surname&gt;Test IV&lt;/surname&gt;
      &lt;given-names&gt;Chris-Eat Meat&lt;/given-names&gt;
      &lt;initials&gt;C. E.&lt;/initials&gt;
   &lt;/name&gt;
   &lt;name&gt;
      &lt;surname&gt;Test V&lt;/surname&gt;
      &lt;given-names&gt;Chris-Eat-Meat&lt;/given-names&gt;
      &lt;initials&gt;C. E.&lt;/initials&gt;
   &lt;/name&gt;
   &lt;name&gt;
      &lt;surname&gt;Test V&lt;/surname&gt;
      &lt;given-names&gt;Chris---Eat----Meat&lt;/given-names&gt;
      &lt;initials&gt;C. E.&lt;/initials&gt;
   &lt;/name&gt;
&lt;/doc&gt;