I previously wrote about how to insert a span into a link, but after testing this method quickly failed when coupled with the Views module. When a Page view is created and assigned to a node url, everything is fine and dandy. The issues arise when you want a link to use an alias. Luckily, I’ve devised a really simple work around:

use the same code to generate your menu:

<?php if (!empty($primary_links)): ?>
<?php print theme('links', $primary_links, array('id' => 'nav')); ?>
<?php endif; ?>

this calls the links() function inside of includes/theme.inc, which triggers the l() function in the includes/common.inc to write the primary links menu. We can’t override the l() entirely because other code uses it, so we’ll copy the code from theme.inc and override the l() function with a site template specific one. Here’s my code:

theme.inc

function mytheme_links($links, $attributes = array('class' => 'links')) {
 global $language;
 $output = '';
 $options = array(
 'class' => '',
 'html' => FALSE,
 );

 if (count($links) > 0) {
 $output = '<ul' . drupal_attributes($attributes) .'>';

 $num_links = count($links);
 $i = 1;

 foreach ($links as $key => $link) {
 $class = $key;

 $links['attributes']['title'] = $link['title'];
 // Add first, last and active classes to the list of links to help out themers.
 if ($i == 1) {
 $class .= ' first';
 }
 if ($i == $num_links) {
 $class .= ' signup last';
 $links['attributes']['class'] .= $class;
 }
 if (isset($link['href']) && ($link['href'] == $_GET['q'] || ($link['href'] == '<front>' && drupal_is_front_page()))
 && (empty($link['language']) || $link['language']->language == $language->language)) {
 $class .= ' active';
 }
 $output .= '<li' . drupal_attributes(array('class' => $class)) .'>';

 if (isset($link['href'])) {
 // Pass in $link as $options, they share the same keys.

//here's my call to the overridden function
 $output .= mytheme_l($link['title'], $link['href'], $links);
 }
 else if (!empty($link['title'])) {
 // Some links are actually not links, but we wrap these in <span> for adding title and class attributes
 if (empty($link['html'])) {
 $link['title'] = check_plain($link['title']);
 }
 $span_attributes = '';
 if (isset($link['attributes'])) {
 $span_attributes = drupal_attributes($link['attributes']);
 }
 $output .= '<span'. $span_attributes .'>'. $link['title'] .'</span>';
 }

 $i++;
 $output .= "</li>\n";
 }

 $output .= '</ul>';
 }

 return $output;
}

and now the l() function in common.inc gets changed to mytheme_l():

function mytheme_l($text, $path, $options = array()) {
 global $language;

 // Merge in defaults.
 $options += array(
 'attributes' => array(),
 'html' => FALSE,
 );

 // Append active class.
 if (($path == $_GET['q'] || ($path == '<front>' && drupal_is_front_page())) &&
 (empty($options['language']) || $options['language']->language == $language->language)) {
 if (isset($options['attributes']['class'])) {
 $options['attributes']['class'] .= ' active';
 }
 else {
 $options['attributes']['class'] = 'active';
 }
 }

 // Remove all HTML and PHP tags from a tooltip. For best performance, we act only
 // if a quick strpos() pre-check gave a suspicion (because strip_tags() is expensive).
 if (isset($options['attributes']['title']) && strpos($options['attributes']['title'], '<') !== FALSE) {
 $options['attributes']['title'] = strip_tags($options['attributes']['title']);
 }
 // Inject a span inside the anchor tag for the purposes of this theme - NOTE - May have to remove check_plain() around
 // the $text variable
 return '<a href="'. check_url(url($path, $options)) .'"'. drupal_attributes($options['attributes']) .'><span>'. ($options['html'] ? $text : check_plain($text)) .'</span></a>';
}

You can see that all I did was include the span element inside the anchor tag using simple php string concatenation. Until I can find a better solution, this does the trick. Don’t ask me about benchmarking, though…