=== added file 'mappers/content_taxonomy.inc'
--- mappers/content_taxonomy.inc  1970-01-01 00:00:00 +0000
+++ mappers/content_taxonomy.inc  2009-11-25 07:38:29 +0000
@@ -0,0 +1,119 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Enable the user of feeds to map taxonomy terms from the feed for the
+ * current node to content_taxonomy CCK fields.
+ */
+
+/**
+ * Implementation of hook_feeds_node_processor_targets_alter().
+ *
+ * @see FeedsNodeProcessor::getMappingTargets().
+ */
+function content_taxonomy_feeds_node_processor_targets_alter(&$targets, $content_type) {
+  $info = content_types($content_type);
+  $fields = array();
+  if (isset($info['fields']) && count($info['fields'])) {
+    foreach ($info['fields'] as $field_name => $field) {
+      if(in_array($field['type'], array('content_taxonomy'))) {
+        $name = isset($field['widget']['label']) ? $field['widget']['label'] : $field_name;
+        $targets[$field_name] = array(
+          'name' => $name,
+          'callback' => 'content_taxonomy_feeds_set_target',
+          'description' => t('The CCK %name field of the node (!type).', array('%name' => $name, '!type' => $field['type'])),
+        );
+      }
+    }
+  }
+}
+
+/**
+ * Callback for mapping. Here is where the actual mapping happens.
+ *
+ * @param $node
+ *   Reference to the node object we are working on.
+ *
+ * @param $vid
+ *   The selected content_taxonomy CCK field.
+ *
+ * @param $terms
+ *   Given terms as array. If a string is used, it is converted to an array
+ *   using <code>taxonomy_terms_parse_string($terms)->tids</code>.
+ *
+ * @see taxonomy_terms_parse_string().
+ *
+ */
+function content_taxonomy_feeds_set_target(&$node, $field_name, $terms) {
+  static $fields = array();
+
+  $field = content_fields($field_name, $node->type);
+
+  // Parse string for multiple tags (comma separated)
+  if(is_string($terms)) {
+    $terms = split(',', $terms);
+  }
+
+  // Return if there are no or empty terms.
+  if (!is_array($terms) || empty($terms)) {
+    return;
+  }
+
+  $tags = $field['widget']['type'] == 'content_taxonomy_autocomplete' && $field['widget']['new_terms'];
+  $multiple = $field['widget']['multiple'];
+
+  foreach ($terms as $k => $term_name) {
+   $term_name = trim($term_name);
+    if ($terms_found = content_taxonomy_get_term_by_name_vid($term_name, $field['vid'])) {
+      // If any terms are found add them to the field by found tid.
+      foreach($terms_found as $term_found) {
+        if(!is_array($node->$field_name)) $node->$field_name = array();
+        array_push($node->$field_name, array('value' => $term_found['tid']));
+        if ($multiple != 0 && count($node->$field_name) >= $multiple) {
+          // If the vocab is not for multiple tags break after the first hit.
+          break;
+        }
+      }
+    } else if ($tags) {
+      // If the field is configured for free tagging, create a new term
+      $edit = array('vid' => $field['vid'], 'name' => $term_name);
+      if ($field['widget']['extra_parent']) {
+        $edit['parent'] = $field['widget']['extra_parent'];
+      }
+      taxonomy_save_term($edit);
+      if (!is_array($node->$field_name)) {
+        $node->$field_name = array();
+      }
+      array_push($node->$field_name, array('value' => $edit['tid']));
+    }
+    if ($multiple != 0 && count($node->$field_name) >= $multiple) {
+      // If the vocab is not for multiple tags break after a first term has been added.
+      break;
+    }
+  }
+}
+
+/**
+ * Try to map a string to an existing term by name and vocabulary id.
+ *
+ * Provides a case-insensitive and trimmed mapping, to maximize the
+ * likelihood of a successful match limited by a vocabulary id.
+ *
+ * @param $name
+ *   Name of the term to search for.
+ *
+ * @param $vid
+ *   The vocabulary's ID.
+ *
+ * @return
+ *   An array of matching term objects.
+ */
+function content_taxonomy_get_term_by_name_vid($name, $vid) {
+  $db_result = db_query(db_rewrite_sql("SELECT t.tid, t.name FROM {term_data} t WHERE LOWER(t.name) = LOWER('%s') AND vid=%d", 't', 'tid'), trim($name), $vid);
+  $result = array();
+  while ($term = db_fetch_array($db_result)) {
+    $result[] = $term;
+  }
+  return $result;
+}
\ No newline at end of file

=== added file 'tests/feeds/content-taxonomy.csv'
--- tests/feeds/content-taxonomy.csv  1970-01-01 00:00:00 +0000
+++ tests/feeds/content-taxonomy.csv  2009-11-24 08:08:32 +0000
@@ -0,0 +1,2 @@
+"title","created","tags","categories","body"
+"Lorem ipsum",1251936720,"lorem, ipsum, dolor, sit, amet","consectetuer,adipiscing","Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat."

=== added file 'tests/feeds_mapper_content_taxonomy.test'
--- tests/feeds_mapper_content_taxonomy.test  1970-01-01 00:00:00 +0000
+++ tests/feeds_mapper_content_taxonomy.test  2009-11-25 07:37:56 +0000
@@ -0,0 +1,186 @@
+<?php
+// $Id:$
+
+require_once(drupal_get_path('module', 'feeds') . '/tests/feeds_mapper_test.inc');
+
+/**
+ * Class for testing Feeds <em>content</em> mapper.
+ */
+class FeedsMapperContentTaxonomyTestCase extends FeedsMapperTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => t('Mapper: Content Taxonomy'),
+      'description' => t('Test Feeds Mapper support for Content Taxonomy CCK fields'),
+      'group' => t('Feeds'),
+    );
+  }
+
+  /**
+   * Set up the
+   */
+  public function setUp() {
+    // Call parent setup with the required module
+    parent::setUp(
+      'feeds', 'feeds_ui', 'ctools', 'content',
+      'taxonomy', 'content_taxonomy', 'content_taxonomy_autocomplete', 'content_taxonomy_options'
+    );
+
+    // Create user and login
+    $this->drupalLogin($this->drupalCreateUser(
+      array(
+        'administer content types',
+        'administer feeds',
+        'administer nodes',
+        'administer site configuration',
+      )
+    ));
+  }
+
+  /**
+   * Basic test loading a single entry CSV file.
+   */
+  public function test() {
+
+    // Create vocabularies
+    $vocabularies = array(
+      'tags' => array(
+        'name' => $this->randomName(),
+        'tags' => true,
+      ),
+      'categories' => array(
+        'name' => $this->randomName(),
+        'tags' => false,
+      )
+    );
+    foreach ($vocabularies as &$vocabulary) {
+      taxonomy_save_vocabulary($vocabulary);
+    }
+
+    // Create terms
+    $terms = array(
+      array(
+        'name' => 'foo',
+        'vid' => $vocabularies['tags']['vid'],
+        'weight' => 0,
+      ),
+      array(
+        'name' => 'lorem',
+        'vid' => $vocabularies['tags']['vid'],
+        'weight' => 0,
+      ),
+      array(
+        'name' => 'ipsum',
+        'vid' => $vocabularies['tags']['vid'],
+        'weight' => 0,
+      ),
+      array(
+        'name' => 'bar',
+        'vid' => $vocabularies['categories']['vid'],
+        'weight' => 0,
+      ),
+      'consectetuer' => array(
+        'name' => 'consectetuer',
+        'vid' => $vocabularies['categories']['vid'],
+        'weight' => 0,
+      ),
+    );
+    foreach ($terms as &$term) {
+      taxonomy_save_term($term);
+    }
+
+    // Create content type
+    $typename = $this->createContentType(NULL, array(
+      'tags' => array(
+        'type' => 'content_taxonomy',
+        'widget' => 'content_taxonomy_autocomplete',
+        'settings' => array(
+          'new_terms' => 'insert',
+          'multiple' =>  '1',
+          'vid' => $vocabularies['tags']['vid'],
+        ),
+      ),
+      'categories' => array(
+        'type' => 'content_taxonomy',
+        'widget' => 'content_taxonomy_select',
+        'settings' => array(
+          'multiple' => '1',
+          'vid' => $vocabularies['categories']['vid'],
+        ),
+      ),
+    ));
+
+    // Create importer configuration
+    $this->createFeedConfiguration('Content Taxonomy CSV', 'csv'); // Create a default importer configuration
+    $this->setSettings('csv', NULL, array('content_type' => '','import_period' => FEEDS_SCHEDULE_NEVER,)); // Importer setting
+    $this->setPlugin('csv', 'FeedsFileFetcher'); //Set fetcher
+    $this->setPlugin('csv', 'FeedsCSVParser'); //Set parser
+    $this->setSettings('csv', 'FeedsNodeProcessor', array('content_type' => $typename)); // Processor settings
+    $this->addMappings('csv', array(
+      array(
+        'source' => 'title',
+        'target' => 'title'
+      ),
+      array(
+        'source' => 'created',
+        'target' => 'created'
+      ),
+      array(
+        'source' => 'body',
+        'target' => 'body'
+      ),
+      array(
+        'source' => 'tags',
+        'target' => 'field_tags'
+      ),
+      array(
+        'source' => 'categories',
+        'target' => 'field_categories'
+      ),
+    ));
+
+    // Import CSV file.
+    $this->importFile('csv', $this->absolutePath() .'/tests/feeds/content-taxonomy.csv');
+    $this->assertText('Created 1 '.$typename.' nodes.');
+
+    // Edit the imported node
+    $this->drupalGet('node/1/edit');
+
+    $this->assertCCKFieldValue('tags', array('lorem', 'ipsum', 'dolor', 'sit', 'amet'));
+    $this->assertCCKFieldValue('categories', $terms['consectetuer']['tid']);
+  }
+
+  protected function selectFieldWidget($field_name, $field_type) {
+    if ($field_type == 'content_taxonomy') {
+      return 'content_taxonomy_select';
+    } else {
+      return parent::selectFieldWidget($field_name, $field_type);
+    }
+  }
+
+  protected function getFormFieldsNames($field_name, $index) {
+    switch ($field_name) {
+      case 'tags':
+        return array("field_{$field_name}[value]");
+      case 'categories':
+        return array("field_{$field_name}[value][]");
+      default:
+        return parent::getFormFieldsNames($field_name, $index);
+    }
+  }
+
+  protected function getFormFieldsValues($field_name, $value) {
+    switch($field_name) {
+      case 'tags':
+        if (is_array($value)) {
+          // @todo sort tags by weight before joining
+          $value = join(', ', $value);
+        }
+        return array($value);
+      case 'categories':
+        // @todo return tid(s) from $value
+      default:
+        return parent::getFormFieldsValues($field_name, $value);
+    }
+  }
+}
