diff --git a/entity_token.tokens.inc b/entity_token.tokens.inc
index 9fe1915..fd6f408 100644
--- a/entity_token.tokens.inc
+++ b/entity_token.tokens.inc
@@ -13,15 +13,15 @@
  */
 function entity_token_types() {
   $return = entity_token_types_chained();
-  return $return + drupal_map_assoc(array('text', 'integer', 'decimal', 'duration', 'boolean', 'uri', 'site'));
+  return $return + drupal_map_assoc(array('text', 'integer', 'decimal', 'duration', 'boolean', 'uri'));
 }
 
 /**
  * Defines a list of token types that need to be chained.
  *
  * @return
- *   If a type is given, whether the given type needs to be chained. Else a full
- *   list of token types to be chained as returned by
+ *   If a (token) type is given, whether the given type needs to be chained.
+ *   Else a full list of token types to be chained as returned by
  *   entity_token_token_types().
  */
 function entity_token_types_chained($type = NULL) {
@@ -31,41 +31,121 @@ function entity_token_types_chained($type = NULL) {
       $return[$token_type] = $entity_type;
     }
   }
-  // Add 'date' tokens.
+  // Add 'date' and 'site' tokens.
   $return['date'] = 'date';
-  return isset($type) ? isset($return[$type]) : $return;
+  $return['site'] = 'site';
+  // Add a 'struct' type.
+  $return['struct'] = 'struct';
+  if (isset($type)) {
+    return (entity_property_list_extract_type($type)) ? TRUE : isset($return[$type]);
+  }
+  return $return;
+}
+
+/**
+ * Gets the right token type for a given property info array.
+ */
+function _entity_token_map_to_token_type($property_info) {
+  $lookup = &drupal_static(__FUNCTION__);
+
+  if (!$lookup) {
+    // Initialize a lookup array mapping property types to token types.
+    $lookup = array_flip(entity_token_types());
+  }
+
+  $type = isset($property_info['type']) ? $property_info['type'] : 'text';
+  // Just use the type 'struct' for all structures.
+  if (!empty($property_info['property info'])) {
+    $type = 'struct';
+  }
+
+  if ($item_type = entity_property_list_extract_type($type)) {
+    return isset($lookup[$item_type]) ? "list<$lookup[$item_type]>" : FALSE;
+  }
+  return isset($lookup[$type]) ? $lookup[$type] : FALSE;
 }
 
 /**
  * Implements hook_token_info_alter().
  */
 function entity_token_token_info_alter(&$info) {
-  $valid_types = entity_token_types();
   $entity_info = entity_get_info();
 
-  foreach ($valid_types as $token_type => $type) {
-    // Just add all properties regardless whether its in a bundle only if there
-    // is no token of the property yet.
+  // Loop over all chain-able token types, as those may contain further tokens,
+  // e.g. entity types or 'site'.
+  foreach (entity_token_types_chained() as $token_type => $type) {
+    // Just add all properties regardless whether it's in a bundle, but only if
+    // there is no token of the property yet.
     foreach (entity_get_all_property_info($type) as $name => $property) {
       $name = str_replace('_', '-', $name);
+      $property += array('type' => 'text', 'description' => $property['label']);
+      $property_token_type = _entity_token_map_to_token_type($property);
+
+      if (!isset($info['tokens'][$token_type][$name]) && $property_token_type) {
 
-      if (!isset($info['tokens'][$token_type][$name]) && (!isset($property['type']) || in_array($property['type'], $valid_types)) && (!isset($property['entity token']) || $property['entity token'])) {
         $info['tokens'][$token_type][$name] = array(
           'name' => $property['label'],
-          'type' => isset($property['type']) ? array_search($property['type'], $valid_types) : 'text',
+          'description' => $property['description'],
+          'type' => $property_token_type,
           // Mark the token so we know we have to provide the value afterwards.
           'entity-token' => TRUE,
         );
-        $info['tokens'][$token_type][$name]['description'] = isset($property['description']) ? $property['description'] : $property['label'];
+      }
+      if ($property_token_type == 'struct' && !empty($property['property info'])) {
+        $info['tokens'][$token_type][$name]['dynamic'] = TRUE;
+        $help = array();
+        foreach ($property['property info'] as $key => $property_info) {
+          $help[] = $key . ' (' . $property_info['label'] .')';
+        }
+        $info['tokens'][$token_type][$name]['description'] .= ' ' . t('The following properties may be appended to the token: @keys',
+          array('@keys' => implode(', ', $help))
+        );
+      }
+    }
+  }
+
+  // Make sure all chain-able token types we support are registered.
+  foreach (entity_token_types_chained() as $token_type => $type) {
+
+    if (!empty($info['tokens'][$token_type]) && !isset($info['types'][$token_type])) {
+      if (isset($entity_info[$type])) {
+        $info['types'][$token_type] = array(
+          'name' => $entity_info[$type]['label'],
+          'description' => t('Tokens related to the "@name" entities.', array('@name' => $entity_info[$type]['label'])),
+          'needs-data' => $token_type,
+        );
+      }
+      else {
+        $info['types'][$token_type] = array(
+          'name' => drupal_strtoupper($token_type),
+          'description' => t('@name tokens.', array('@name' => drupal_strtoupper($token_type))),
+          'needs-data' => $token_type,
+        );
       }
     }
-    // Make sure there is a token category for each supported entity.
-    if (!empty($info['tokens'][$token_type]) && !isset($info['types'][$token_type]) && isset($entity_info[$type])) {
-      $info['types'][$token_type] = array(
-        'name' => $entity_info[$type]['label'],
-        'description' => t('Tokens related to the %name entities.', array('%name' => $entity_info[$type]['label'])),
-        'needs-data' => $token_type,
-      );
+    if (!empty($info['tokens'][$token_type]) && !isset($info['types']["list<$token_type>"]) && $token_type != 'site') {
+      if (isset($entity_info[$type])) {
+        $info['types']["list<$token_type>"] = array(
+          'name' => t('List of @entities', array('@entities' => isset($entity_info[$type]['plural label']) ? $entity_info[$type]['plural label'] : $entity_info[$type]['label'] . 's')),
+          'description' => t('Tokens related to the "@name" entities.', array('@name' => $entity_info[$type]['label'])),
+          'needs-data' => "list<$token_type>",
+        );
+      }
+      else {
+        $info['types']["list<$token_type>"] = array(
+          'name' => t('List of @type values', array('@type' => $token_type)),
+          'description' => t('Tokens for lists of @type values.', array('@type' => $token_type)),
+          'needs-data' => "list<$token_type>",
+        );
+      }
+      // Also add some basic token replacements for lists...
+      for ($i = 0; $i < 4; $i++) {
+        $info['tokens']["list<$token_type>"][$i] = array(
+          'name' => t('@type with delta @delta', array('@delta' => $i, '@type' => $info['types'][$token_type]['name'])),
+          'description' => t('The list item with delta @delta. Delta values start from 0 and are incremented by one per list item.', array('@delta' => $i)),
+          'type' => $token_type,
+        );
+      }
     }
   }
 }
@@ -74,50 +154,117 @@ function entity_token_token_info_alter(&$info) {
  * Implements hook_tokens().
  */
 function entity_token_tokens($type, $tokens, array $data = array(), array $options = array()) {
-  $token_types = entity_token_types();
+  $token_types = entity_token_types_chained();
+  $replacements = array();
+
   if (isset($token_types[$type]) && (!empty($data[$type]) || $type == 'site')) {
     $data += array($type => FALSE);
-    $replacements = array();
 
     $info = token_info();
     foreach ($tokens as $name => $original) {
       // Provide the token for all properties marked to stem from us.
-      if (!empty($info['tokens'][$type][$name]['entity-token'])) {
+      if (!empty($info['tokens'][$type][$name]['entity-token']) || $type == 'struct') {
         $wrapper = !isset($wrapper) ? _entity_token_wrap_data($type, $token_types[$type], $data[$type], $options) : $wrapper;
         $property_name = str_replace('-', '_', $name);
         try {
           $replacements[$original] = _entity_token_get_token($wrapper->$property_name, $options);
         }
         catch (EntityMetadataWrapperException $e) {
-          // In case tokens for not existing values are requested, just do nothing.
+          // If tokens for not existing values are requested, just do nothing.
         }
       }
     }
+
     // Properly chain everything of a type marked as needs chaining.
+    $info['tokens'] += array($type => array());
     foreach ($info['tokens'][$type] as $name => $token_info) {
       if (!empty($token_info['entity-token']) && isset($token_info['type']) && entity_token_types_chained($token_info['type'])) {
+
         if ($chained_tokens = token_find_with_prefix($tokens, $name)) {
           $wrapper = !isset($wrapper) ? _entity_token_wrap_data($type, $token_types[$type], $data[$type], $options) : $wrapper;
           $property_name = str_replace('-', '_', $name);
+
+          try {
+            // Pass on 'struct' properties wrapped, else un-wrap the data.
+            $value = ($token_info['type'] == 'struct') ? $wrapper->$property_name : $wrapper->$property_name->value();
+            $replacements += token_generate($token_info['type'], $chained_tokens, array($token_info['type'] => $value), $options);
+          }
+          catch (EntityMetadataWrapperException $e) {
+            // If tokens for not existing values are requested, just do nothing.
+          }
+        }
+      }
+    }
+  }
+  // Add support for evaluating tokens for "list<type"> types.
+  elseif ($item_token_type = entity_property_list_extract_type($type)) {
+    foreach ($tokens as $name => $original) {
+      // Care about getting entries of a list.
+      if (is_numeric($name)) {
+        $wrapper = !isset($wrapper) ? _entity_token_wrap_data($type, "list<$token_types[$item_token_type]>", $data[$type], $options) : $wrapper;
+        try {
+          $replacements[$original] = _entity_token_get_token($wrapper->get($name), $options);
+        }
+        catch (EntityMetadataWrapperException $e) {
+          // If tokens for not existing values are requested, just do nothing.
+        }
+      }
+      // Care about generating chained tokens for list-items.
+      else {
+        $parts = explode(':', $name, 2);
+        $delta = $parts[0];
+
+        if (is_numeric($delta) && $chained_tokens = token_find_with_prefix($tokens, $delta)) {
+          $wrapper = !isset($wrapper) ? _entity_token_wrap_data($type, "list<$token_types[$item_token_type]>", $data[$type], $options) : $wrapper;
           try {
-            $replacements += token_generate($token_info['type'], $chained_tokens, array($token_info['type'] => $wrapper->$property_name->value()), $options);
+            $replacements += token_generate($item_token_type, $chained_tokens, array($item_token_type => $wrapper->get($delta)->value()), $options);
           }
           catch (EntityMetadataWrapperException $e) {
-            // In case tokens for not existing values are requested, just do nothing.
+            // If tokens for not existing values are requested, just do nothing.
           }
         }
       }
     }
-    return $replacements;
   }
+
+  // Add support for chaining struct data. As struct data has no registered
+  // tokens, we have to chain based upon wrapper property info.
+  if ($type == 'struct') {
+    $wrapper = $data[$type];
+    foreach ($wrapper as $name => $property) {
+      $token_type = _entity_token_map_to_token_type($property->type());
+
+      if (entity_token_types_chained($token_type) && $chained_tokens = token_find_with_prefix($tokens, $name)) {
+        try {
+          // Pass on 'struct' properties wrapped, else un-wrap the data.
+          $value = ($token_type == 'struct') ? $property : $property->value();
+          $replacements += token_generate($token_type, $chained_tokens, array($token_type => $value), $options);
+        }
+        catch (EntityMetadataWrapperException $e) {
+          // If tokens for not existing values are requested, just do nothing.
+        }
+      }
+    }
+  }
+
+  return $replacements;
 }
 
 /**
  * Wraps the given data by correctly obeying the options.
  */
 function _entity_token_wrap_data($token_type, $type, $data, $options) {
-  $wrapper = ($type == 'site') ? entity_metadata_site_wrapper() : entity_metadata_wrapper($type, $data);
-  if (isset($options['language'])) {
+  if ($type == 'site') {
+    $wrapper = entity_metadata_site_wrapper();
+  }
+  elseif ($type == 'struct') {
+    // 'struct' data items are passed on wrapped.
+    $wrapper = $data;
+  }
+  else {
+    $wrapper = entity_metadata_wrapper($type, $data);
+  }
+  if (isset($options['language']) && $wrapper instanceof EntityStructureWrapper) {
     $wrapper->language($options['language']->language);
   }
   return $wrapper;
@@ -154,4 +301,16 @@ function _entity_token_get_token($wrapper, $options) {
     case 'text':
       return $wrapper->value($options);
   }
+
+  // Care for outputing list values.
+  if ($wrapper instanceof EntityListWrapper) {
+    $output = array();
+    foreach ($wrapper as $item) {
+      $output[] = _entity_token_get_token($item, $options);
+    }
+    return implode(', ', $output);
+  }
+  // Else we do not have a good string to output, e.g. for struct values. Just
+  // output the string representation of the wrapper.
+  return (string) $wrapper;
 }
diff --git a/includes/entity.property.inc b/includes/entity.property.inc
index 86bae62..63273a4 100644
--- a/includes/entity.property.inc
+++ b/includes/entity.property.inc
@@ -536,12 +536,12 @@ function entity_property_field_item_image_info() {
   );
   $properties['alt'] = array(
     'type' => 'text',
-    'label' => t('The <em>Alt</em> attribute text'),
+    'label' => t('The "Alt" attribute text'),
     'setter callback' => 'entity_property_verbatim_set',
   );
   $properties['title'] = array(
     'type' => 'text',
-    'label' => t('The <em>Title</em> attribute text'),
+    'label' => t('The "Title" attribute text'),
     'setter callback' => 'entity_property_verbatim_set',
   );
   return $properties;
diff --git a/modules/field.info.inc b/modules/field.info.inc
index 23a000f..6e6c906 100644
--- a/modules/field.info.inc
+++ b/modules/field.info.inc
@@ -53,7 +53,7 @@ function entity_metadata_field_default_property_callback(&$info, $entity_type, $
     $property = $instance['property info'] + array(
       'label' => $instance['label'],
       'type' => $field_type['property_type'],
-      'description' => t('Field "@name"', array('@name' => $name)),
+      'description' => t('Field "@name".', array('@name' => $name)),
       'getter callback' => 'entity_metadata_field_property_get',
       'setter callback' => 'entity_metadata_field_property_set',
       'access callback' => 'entity_metadata_field_access_callback',
