Index: modules/taxonomy/taxonomy.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.admin.inc,v
retrieving revision 1.53
diff -u -p -r1.53 taxonomy.admin.inc
--- modules/taxonomy/taxonomy.admin.inc	4 Jun 2009 03:33:29 -0000	1.53
+++ modules/taxonomy/taxonomy.admin.inc	5 Jun 2009 14:30:34 -0000
@@ -102,11 +102,13 @@ function theme_taxonomy_overview_vocabul
  * @see taxonomy_form_vocabulary_submit()
  */
 function taxonomy_form_vocabulary(&$form_state, $edit = array()) {
+  drupal_add_js(drupal_get_path('module', 'taxonomy') . '/vocabulary.js');
   if (!is_array($edit)) {
     $edit = (array)$edit;
   }
   $edit += array(
     'name' => '',
+    'machine_name' => '',
     'description' => '',
     'help' => '',
     'nodes' => array(),
@@ -117,6 +119,7 @@ function taxonomy_form_vocabulary(&$form
     'required' => 0,
     'weight' => 0,
   );
+  $form['#vocabulary'] = (object) $edit;
   // Check whether we need a deletion confirmation form.
   if (isset($form_state['confirm_delete']) && isset($form_state['values']['vid'])) {
     return taxonomy_vocabulary_confirm_delete($form_state, $form_state['values']['vid']);
@@ -128,6 +131,15 @@ function taxonomy_form_vocabulary(&$form
     '#maxlength' => 255,
     '#description' => t('The name for this vocabulary, e.g., <em>"Tags"</em>.'),
     '#required' => TRUE,
+    '#field_suffix' => ' <small id="vocabulary-name-suffix">&nbsp;</small>',
+  );
+  $form['machine_name'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Machine readable name'),
+    '#default_value' => $edit['machine_name'],
+    '#maxlength' => 255,
+    '#description' => t('The machine readable name for this vocabulary, for internal use only.'),
+    '#required' => TRUE,
   );
   $form['help'] = array(
     '#type' => 'textfield',
@@ -194,9 +206,21 @@ function taxonomy_form_vocabulary(&$form
 }
 
 /**
+ * Validation handler for the vocabulary form.
+ *
+ * @see taxonomy_form_vocabulary()
+ */
+function taxonomy_form_vocabulary_validate($form, &$form_state) {
+  if (!preg_match('!^[a-z0-9_]+$!', $form_state['values']['machine_name'])) {
+    form_set_error('machine_name', t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
+  }
+}
+
+/**
  * Accept the form submission for a vocabulary and save the results.
  */
 function taxonomy_form_vocabulary_submit($form, &$form_state) {
+  $old_vocabulary = $form['#vocabulary'];
   if ($form_state['clicked_button']['#value'] == t('Delete')) {
     // Rebuild the form to confirm vocabulary deletion.
     $form_state['rebuild'] = TRUE;
@@ -206,6 +230,9 @@ function taxonomy_form_vocabulary_submit
   // Fix up the nodes array to remove unchecked nodes.
   $form_state['values']['nodes'] = array_filter($form_state['values']['nodes']);
   $vocabulary = (object) $form_state['values'];
+  if ($vocabulary->machine_name != $old_vocabulary->machine_name) {
+    field_attach_rename_bundle($old_vocabulary->machine_name, $vocabulary->machine_name);
+  }
   switch (taxonomy_vocabulary_save($vocabulary)) {
     case SAVED_NEW:
       drupal_set_message(t('Created new vocabulary %name.', array('%name' => $vocabulary->name)));
@@ -636,6 +663,7 @@ function taxonomy_form_term(&$form_state
   $edit += array(
     'name' => '',
     'description' => '',
+    'machine_name' => $vocabulary->machine_name,
     'tid' => NULL,
     'weight' => 0,
   );
@@ -672,6 +700,15 @@ function taxonomy_form_term(&$form_state
     '#default_value' => $edit['description'],
     '#description' => t('A description of the term. To be displayed on taxonomy/term pages and RSS feeds.'));
 
+  $form['machine_name'] = array(
+    '#type' => 'textfield',
+    '#access' => FALSE,
+    '#value' => isset($edit['machine_name']) ? $edit['machine_name'] : $vocabulary->name,
+  );
+
+
+  field_attach_form('taxonomy_term', (object) $edit, $form, $form_state);
+
   $form['advanced'] = array(
     '#type' => 'fieldset',
     '#title' => t('Advanced options'),
@@ -731,11 +768,14 @@ function taxonomy_form_term(&$form_state
 }
 
 /**
- * Validation handler for the term edit form. Ensure numeric weight values.
+ * Validation handler for the term form.
  *
  * @see taxonomy_form_term()
  */
 function taxonomy_form_term_validate($form, &$form_state) {
+  field_attach_form_validate('taxonomy term', (object) $form_state['values'], $form, $form_state);
+
+  // Ensure numeric values.
   if (isset($form_state['values']['weight']) && !is_numeric($form_state['values']['weight'])) {
     form_set_error('weight', t('Weight value must be numeric.'));
   }
@@ -765,6 +805,8 @@ function taxonomy_form_term_submit($form
   }
 
   $term = (object) $form_state['values'];
+  field_attach_submit('taxonomy term', $term, $form, $form_state);
+
   $status = taxonomy_term_save($term);
   switch ($status) {
     case SAVED_NEW:
Index: modules/taxonomy/taxonomy.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.install,v
retrieving revision 1.18
diff -u -p -r1.18 taxonomy.install
--- modules/taxonomy/taxonomy.install	1 Jun 2009 22:07:10 -0000	1.18
+++ modules/taxonomy/taxonomy.install	5 Jun 2009 14:30:34 -0000
@@ -226,6 +226,13 @@ function taxonomy_schema() {
         'default' => '',
         'description' => 'Name of the vocabulary.',
       ),
+      'machine_name' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The vocabulary machine name.',
+      ),
       'description' => array(
         'type' => 'text',
         'not null' => FALSE,
@@ -345,3 +352,27 @@ function taxonomy_update_7001() {
 
   return $ret;
 }
+
+/**
+ * Add vocabulary machine_name column.
+ */
+function taxonomy_update_7002() {
+  $ret = array();
+  $field = array(
+    'type' => 'varchar',
+    'length' => 255,
+    'not null' => TRUE,
+    'default' => '',
+    'description' => 'The vocabulary machine name.',
+  );
+
+  db_add_field($ret, 'taxonomy_vocabulary', 'machine_name', $field);
+
+  foreach (taxonomy_get_vocabularies() as $vid => $vocabulary) {
+    db_update('taxonomy_vocabulary')
+      ->fields(array('machine_name' => 'vocabulary_' . $vid))
+      ->condition('vid', $vid)
+      ->execute();
+  }
+  return $ret;
+}
Index: modules/taxonomy/taxonomy.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v
retrieving revision 1.476
diff -u -p -r1.476 taxonomy.module
--- modules/taxonomy/taxonomy.module	3 Jun 2009 19:34:53 -0000	1.476
+++ modules/taxonomy/taxonomy.module	5 Jun 2009 14:30:35 -0000
@@ -19,6 +19,35 @@ function taxonomy_perm() {
 }
 
 /**
+ * Implement hook_fieldable().
+ */
+function taxonomy_fieldable_info() {
+  $return = array(
+    'taxonomy_term' => array(
+      'name' => t('Taxonomy term'),
+      'id key' => 'tid',
+      'bundle key' => 'machine_name',
+    ),
+  );
+  return $return;
+}
+
+/**
+ * Implement hook_field_build_modes();
+ *
+ * @TODO: build mode for display as a field (when attached to nodes etc.).
+ */
+function taxonomy_field_build_modes($obj_type) {
+  $modes = array();
+  if ($obj_type == 'term') {
+    $modes = array(
+      'full' => t('Taxonomy term page'),
+    );
+  }
+  return $modes;
+}
+
+/**
  * Implement hook_theme().
  */
 function taxonomy_theme() {
@@ -259,6 +288,7 @@ function taxonomy_vocabulary_save($vocab
       }
       $query->execute();
     }
+    field_attach_create_bundle($vocabulary->machine_name);
     module_invoke_all('taxonomy_vocabulary_insert', $vocabulary);
   }
 
@@ -290,6 +320,7 @@ function taxonomy_vocabulary_delete($vid
     taxonomy_term_delete($tid);
   }
 
+  field_attach_delete_bundle($vocabulary['machine_name']);
   module_invoke_all('taxonomy', 'delete', 'vocabulary', $vocabulary);
 
   cache_clear_all();
@@ -353,12 +384,16 @@ function taxonomy_term_save($term) {
     $term->name = trim($term->name);
   }
 
+  field_attach_presave('taxonomy_term', $term);
+
   if (!empty($term->tid) && $term->name) {
     $status = drupal_write_record('taxonomy_term_data', $term, 'tid');
+    field_attach_update('taxonomy_term', $term);
     module_invoke_all('taxonomy_term_insert', $term);
   }
   else {
     $status = drupal_write_record('taxonomy_term_data', $term);
+    field_attach_insert('taxonomy_term', $term);
     module_invoke_all('taxonomy_term_update', $term);
   }
 
@@ -484,6 +519,7 @@ function taxonomy_term_delete($tid) {
         ->condition('tid', $tid)
         ->execute();
 
+      field_attach_delete('taxonomy term', $term);
       module_invoke_all('taxonomy_term_delete', $term);
     }
 
@@ -786,6 +822,7 @@ function taxonomy_node_save($node, $term
     unset($terms['tags']);
 
     foreach ($typed_input as $vid => $vid_value) {
+      $vocabulary = taxonomy_vocabulary_load($vid);
       $typed_terms = drupal_explode_tags($vid_value);
 
       $inserted = array();
@@ -801,7 +838,11 @@ function taxonomy_node_save($node, $term
         }
 
         if (!$typed_term_tid) {
-          $edit = array('vid' => $vid, 'name' => $typed_term);
+          $edit = array(
+            'vid' => $vid,
+            'name' => $typed_term,
+            'machine_name' => $vocabulary->machine_name,
+          );
           $term = (object)$edit;
           $status = taxonomy_term_save($term);
           $typed_term_tid = $term->tid;
@@ -1338,8 +1379,10 @@ function taxonomy_term_load_multiple($ti
   // $tids still to load, or if $conditions was passed without $tids.
   if ($tids || ($conditions && !$passed_tids)) {
     $query = db_select('taxonomy_term_data', 't');
+    $query->join('taxonomy_vocabulary', 'v', 't.vid = v.vid');
     $taxonomy_term_data = drupal_schema_fields_sql('taxonomy_term_data');
     $query->fields('t', $taxonomy_term_data);
+    $query->addField('v', 'machine_name');
 
     // If the $tids array is populated, add those to the query.
     if ($tids) {
@@ -1358,9 +1401,14 @@ function taxonomy_term_load_multiple($ti
       }
     }
     $queried_terms = $query->execute()->fetchAllAssoc('tid');
-    // Invoke hook_taxonomy_term_load() on the terms loaded from the database
-    // and add them to the static cache.
+
     if (!empty($queried_terms)) {
+
+      // Attach fields.
+      field_attach_load('taxonomy_term', $queried_terms);
+
+      // Invoke hook_taxonomy_term_load() and add the term objects to the
+      // static cache.
       foreach (module_implements('taxonomy_term_load') as $module) {
         $function = $module . '_taxonomy_term_load';
         $function($queried_terms);
Index: modules/taxonomy/taxonomy.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.pages.inc,v
retrieving revision 1.28
diff -u -p -r1.28 taxonomy.pages.inc
--- modules/taxonomy/taxonomy.pages.inc	24 May 2009 17:39:35 -0000	1.28
+++ modules/taxonomy/taxonomy.pages.inc	5 Jun 2009 14:30:35 -0000
@@ -50,9 +50,11 @@ function taxonomy_term_page($terms, $dep
           drupal_add_css(drupal_get_path('module', 'taxonomy') . '/taxonomy.css');
 
           $build = array();
-          // Only display the description if we have a single term, to avoid clutter and confusion.
+          // Only display fields if we have a single term, to avoid clutter and
+          // confusion.
           if (count($tids) == 1) {
             $term = taxonomy_term_load($tids[0]);
+            $build += field_attach_view('taxonomy_term', $term);
             if (!empty($term->description)) {
               $build['term_description'] = array(
                 '#markup' => filter_xss_admin($term->description),
Index: modules/taxonomy/taxonomy.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.test,v
retrieving revision 1.32
diff -u -p -r1.32 taxonomy.test
--- modules/taxonomy/taxonomy.test	3 Jun 2009 19:34:53 -0000	1.32
+++ modules/taxonomy/taxonomy.test	5 Jun 2009 14:30:35 -0000
@@ -19,6 +19,7 @@ class TaxonomyWebTestCase extends Drupal
     $vocabulary = new stdClass();
     $vocabulary->name = $this->randomName();
     $vocabulary->description = $this->randomName();
+    $vocabulary->machine_name = $this->randomName();
     $vocabulary->help = '';
     $vocabulary->nodes = array('article' => 'article');
     $vocabulary->weight = mt_rand(0, 10);
@@ -29,10 +30,11 @@ class TaxonomyWebTestCase extends Drupal
   /**
    * Returns a new term with random properties in vocabulary $vid.
    */
-  function createTerm($vid) {
+  function createTerm($vocabulary) {
     $term = new stdClass();
     $term->name = $this->randomName();
-    $term->vid = $vid;
+    $term->vid = $vocabulary->vid;
+    $term->machine_name = $vocabulary->machine_name;
     taxonomy_term_save($term);
     return $term;
   }
@@ -70,6 +72,7 @@ class TaxonomyVocabularyFunctionalTest e
     $edit = array();
     $edit['name'] = $this->randomName();
     $edit['description'] = $this->randomName();
+    $edit['machine_name'] = $this->randomName();
     $edit['help'] = $this->randomName();
     $edit['nodes[article]'] = 'article';
     $edit['tags'] = 1;
@@ -125,13 +128,11 @@ class TaxonomyVocabularyFunctionalTest e
     // Delete all vocabularies.
     $vocabularies = taxonomy_get_vocabularies();
     foreach ($vocabularies as $key => $vocabulary) {
-      $edit = array();
-      $this->drupalPost('admin/content/taxonomy/' . $vocabulary->vid, $edit, t('Delete'));
-      // Submit the confirm form for deletion.
-      $this->drupalPost(NULL, NULL, t('Delete'));
+      taxonomy_vocabulary_delete($key);
     }
     // Confirm that no vocabularies are found in the database.
     $this->assertFalse(taxonomy_get_vocabularies(), t('No vocabularies found in the database'));
+    $this->drupalGet('admin/content/taxonomy');
     // Check the default message for no vocabularies.
     $this->assertText(t('No vocabularies available.'), t('No vocabularies were found.'));
   }
@@ -143,6 +144,7 @@ class TaxonomyVocabularyFunctionalTest e
     // Create a vocabulary.
     $edit = array(
       'name' => $this->randomName(),
+      'machine_name' => $this->randomName(),
       'nodes[article]' => 'article',
     );
     $this->drupalPost('admin/content/taxonomy/add', $edit, t('Save'));
@@ -306,9 +308,9 @@ class TaxonomyTermUnitTest extends Taxon
   function testTaxonomyTermCountNodes() {
     // Create a vocabulary with three terms.
     $vocabulary = $this->createVocabulary();
-    $term1 = $this->createTerm($vocabulary->vid);
-    $term2 = $this->createTerm($vocabulary->vid);
-    $term3 = $this->createTerm($vocabulary->vid);
+    $term1 = $this->createTerm($vocabulary);
+    $term2 = $this->createTerm($vocabulary);
+    $term3 = $this->createTerm($vocabulary);
 
     // Attach term1 to a node.
     $node1 = $this->drupalCreateNode(array('type' => 'page'));
@@ -382,8 +384,8 @@ class TaxonomyTermTestCase extends Taxon
    */
   function testTaxonomyTermRelations() {
     // Create two taxonomy terms.
-    $term1 = $this->createTerm($this->vocabulary->vid);
-    $term2 = $this->createTerm($this->vocabulary->vid);
+    $term1 = $this->createTerm($this->vocabulary);
+    $term2 = $this->createTerm($this->vocabulary);
 
     // Edit $term1 and add $term2 as a relationship.
     $edit = array();
@@ -392,7 +394,7 @@ class TaxonomyTermTestCase extends Taxon
     $related = taxonomy_get_related($term1->tid);
     $this->assertTrue(isset($related[$term2->tid]), t('Related term was found'));
     // Create a third term.
-    $term3 = $this->createTerm($this->vocabulary->vid);
+    $term3 = $this->createTerm($this->vocabulary);
     $edit['relations[]'] = $term3->tid;
     $this->drupalPost('taxonomy/term/' . $term1->tid . '/edit', $edit, t('Save'));
 
@@ -406,7 +408,7 @@ class TaxonomyTermTestCase extends Taxon
    */
   function testTaxonomySynonyms() {
     // Create a taxonomy term with one synonym.
-    $term = $this->createTerm($this->vocabulary->vid);
+    $term = $this->createTerm($this->vocabulary);
     $term->synonyms = $this->randomName();
     taxonomy_term_save($term);
 
@@ -425,8 +427,8 @@ class TaxonomyTermTestCase extends Taxon
    */
   function testTaxonomyTermHierarchy() {
     // Create two taxonomy terms.
-    $term1 = $this->createTerm($this->vocabulary->vid);
-    $term2 = $this->createTerm($this->vocabulary->vid);
+    $term1 = $this->createTerm($this->vocabulary);
+    $term2 = $this->createTerm($this->vocabulary);
 
     // Edit $term2, setting $term1 as parent.
     $edit = array();
@@ -440,7 +442,7 @@ class TaxonomyTermTestCase extends Taxon
     $this->assertTrue(isset($parents[$term1->tid]), t('Parent found correctly.'));
 
     // Create a third term and save this as a parent of term2.
-    $term3 = $this->createTerm($this->vocabulary->vid);
+    $term3 = $this->createTerm($this->vocabulary);
     $term2->parent = array($term1->tid, $term3->tid);
     taxonomy_term_save($term2);
     $parents = taxonomy_get_parents($term2->tid);
@@ -454,8 +456,8 @@ class TaxonomyTermTestCase extends Taxon
    */
   function testTaxonomyNode() {
     // Create two taxonomy terms.
-    $term1 = $this->createTerm($this->vocabulary->vid);
-    $term2 = $this->createTerm($this->vocabulary->vid);
+    $term1 = $this->createTerm($this->vocabulary);
+    $term2 = $this->createTerm($this->vocabulary);
 
     // Post an article.
     $edit = array();
@@ -566,7 +568,7 @@ class TaxonomyTermTestCase extends Taxon
    * Test taxonomy_get_term_by_name().
    */
   function testTaxonomyGetTermByName() {
-    $term = $this->createTerm($this->vocabulary->vid);
+    $term = $this->createTerm($this->vocabulary);
 
     // Load the term with the exact name.
     $terms = taxonomy_get_term_by_name($term->name);
@@ -625,7 +627,7 @@ class TaxonomyLoadMultipleUnitTest exten
     $i = 0;
     while ($i < 5) {
       $i++;
-      $this->createTerm($vocabulary->vid);
+      $this->createTerm($vocabulary);
     }
     // Load the terms from the vocabulary.
     $terms = taxonomy_term_load_multiple(NULL, array('vid' => $vocabulary->vid));
@@ -653,7 +655,7 @@ class TaxonomyLoadMultipleUnitTest exten
     $this->assertFalse(isset($terms4[$deleted->tid]));
 
     // Create a single term and load it by name.
-    $term = $this->createTerm($vocabulary->vid);
+    $term = $this->createTerm($vocabulary);
     $loaded_terms = taxonomy_term_load_multiple(array(), array('name' => $term->name));
     $this->assertEqual(count($loaded_terms), 1, t('One term was loaded'));
     $loaded_term = reset($loaded_terms);
@@ -664,7 +666,7 @@ class TaxonomyLoadMultipleUnitTest exten
 /**
  * Tests for taxonomy hook invocation.
  */
-class TaxonomyHooksTestCase extends DrupalWebTestCase {
+class TaxonomyHooksTestCase extends TaxonomyWebTestCase {
   public static function getInfo() {
     return array(
       'name' => t('Taxonomy term hooks'),
@@ -683,18 +685,14 @@ class TaxonomyHooksTestCase extends Drup
    * Test that hooks are run correctly on creating, editing and deleting a term.
    */
   function testTaxonomyTermHooks() {
-    // Create a taxonomy vocabulary.
-    $edit = array(
-      'name' => $this->randomName(),
-    );
-    $this->drupalPost('admin/content/taxonomy/add', $edit, t('Save'));
+    $vocabulary = $this->createVocabulary();
 
     // Create a term with one antonym.
     $edit = array(
       'name' => $this->randomName(),
       'antonyms' => 'Long',
     );
-    $this->drupalPost('admin/content/taxonomy/1/add', $edit, t('Save'));
+    $this->drupalPost('admin/content/taxonomy/' . $vocabulary->vid . '/add', $edit, t('Save'));
     $term = reset(taxonomy_get_term_by_name($edit['name']));
     $this->assertEqual($term->antonyms[0], $edit['antonyms'], t('Antonyms were loaded into the term object'));
 
Index: modules/taxonomy/vocabulary.js
===================================================================
RCS file: modules/taxonomy/vocabulary.js
diff -N modules/taxonomy/vocabulary.js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/taxonomy/vocabulary.js	5 Jun 2009 14:30:35 -0000
@@ -0,0 +1,29 @@
+// $Id$
+(function ($) {
+
+Drupal.behaviors.contentTypes = {
+  attach: function () {
+    if ($('#edit-machine-name').val() == $('#edit-name').val().toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/_+/g, '_') || $('#edit-machine-name').val() == '') {
+      $('#edit-machine-name-wrapper').hide();
+      $('#edit-name').keyup(function () {
+        var machine = $(this).val().toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/_+/g, '_');
+        if (machine != '_' && machine != '') {
+          $('#edit-machine-name').val(machine);
+          $('#vocabulary-name-suffix').empty().append(' Machine name: ' + machine + ' [').append($('<a href="#">' + Drupal.t('Edit') + '</a>').click(function () {
+            $('#edit-machine-name-wrapper').show();
+            $('#vocabulary-name-suffix').hide();
+            $('#edit-name').unbind('keyup');
+            return false;
+          })).append(']');
+        }
+        else {
+          $('#edit-machine-name').val(machine);
+          $('#vocabulary-name-suffix').text('');
+        }
+      });
+      $('#edit-name').keyup();
+    }
+  }
+};
+
+})(jQuery);
Index: profiles/default/default.profile
===================================================================
RCS file: /cvs/drupal/drupal/profiles/default/default.profile,v
retrieving revision 1.46
diff -u -p -r1.46 default.profile
--- profiles/default/default.profile	4 Jun 2009 20:09:29 -0000	1.46
+++ profiles/default/default.profile	5 Jun 2009 14:30:35 -0000
@@ -203,6 +203,7 @@ function default_profile_tasks(&$task, $
   $vid = db_insert('taxonomy_vocabulary')->fields(array(
     'name' => 'Tags',
     'description' => $description,
+    'machine_name' => 'tags',
     'help' => $help,
     'relations' => 0,
     'hierarchy' => 0,
