Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.930
diff -u -p -r1.930 common.inc
--- includes/common.inc	4 Jul 2009 18:26:42 -0000	1.930
+++ includes/common.inc	7 Jul 2009 09:24:06 -0000
@@ -2652,6 +2652,84 @@ function drupal_clear_css_cache() {
 }
 
 /**
+ * Prepare a string for use as a valid CSS identifier (class or ID selector).
+ *
+ * http://www.w3.org/TR/CSS21/syndata.html#characters shows the syntax for valid
+ * CSS identifiers (including element names, classes, and IDs in selectors.)
+ *
+ * @param $identifier
+ *   The identifier to clean.
+ * @param $coding_standards
+ *   Enforce Drupal's coding standards on the identifier string.
+ * @param $safe_prefix
+ *   If the proposed identifier starts with an illegal sequence (a digit or a
+ *   dash followed by a digit), prepend a safe string to the identifier;
+ *   defaults to 'class-'.
+ * @return
+ *   The cleaned identifier
+ */
+function drupal_css_class($identifier, $coding_standards = TRUE, $safe_prefix = 'class-') {
+  // Valid characters are:
+  // - the hyphen (U+002D)
+  // - a-z (U+0030 - U+0039)
+  // - A-Z (U+0041 - U+005A)
+  // - the underscore (U+005F)
+  // - 0-9 (U+0061 - U+007A)
+  // - ISO 10646 characters U+00A1 and higher
+  // We strip out any character not in the above list.
+  $identifier = preg_replace('/[\u0000-\u002C\u002E-\u002F\u003A-\u0040\u005B-\u005E\u0060\u007B-\u00A0]/', '', $identifier);
+
+  // Drupal's coding standards use lowercase characters and dashes instead of
+  // underscores.
+  if ($coding_standards) {
+    $identifier = str_replace('_', '-', drupal_strtolower($identifier));
+  }
+
+  // CSS identifiers can't start with a number or a dash and a number, so we
+  // prepend a safe string to prevent an invalid identifier.
+  $start = substr($identifier, 0, 1);
+  if ($start == '-') {
+    $start = substr($identifier, 1, 2);
+  }
+  if (is_numeric($start)) {
+    $identifier = $safe_prefix . $identifier;
+  }
+
+  return $identifier;
+}
+
+/**
+ * Prepare an HTML ID attribute string.
+ *
+ * Remove invalid characters and guarantee uniqueness.
+ *
+ * @param $id
+ *   The ID to clean.
+ * @return
+ *   The cleaned ID.
+ */
+function drupal_css_id($id) {
+  $seen_ids = &drupal_static(__FUNCTION__, array());
+  $id = str_replace(array('][', '_', ' '), '-', $id);
+  $id = drupal_css_class($id, TRUE, 'id-');
+
+  // Ensure IDs are unique. The first occurrence is held but left alone.
+  // Subsequent occurrences get a number appended to them. This incrementing
+  // will almost certainly break code that relies on explicit HTML IDs in
+  // forms that appear more than once on the page, but the alternative is
+  // outputting duplicate IDs, which would break JS code and XHTML
+  // validity anyways. For now, it's an acceptable stopgap solution.
+  if (isset($seen_ids[$id])) {
+    $id = $id . '-' . $seen_ids[$id]++;
+  }
+  else {
+    $seen_ids[$id] = 1;
+  }
+
+  return $id;
+}
+
+/**
  * Add a JavaScript file, setting or inline code to the page.
  *
  * The behavior of this function depends on the parameters it is called with.
Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.348
diff -u -p -r1.348 form.inc
--- includes/form.inc	6 Jul 2009 13:31:47 -0000	1.348
+++ includes/form.inc	7 Jul 2009 09:24:06 -0000
@@ -491,12 +491,12 @@ function drupal_process_form($form_id, &
   if ($form_state['process_input']) {
     drupal_validate_form($form_id, $form, $form_state);
 
-    // form_clean_id() maintains a cache of element IDs it has seen,
+    // drupal_css_id() maintains a cache of element IDs it has seen,
     // so it can prevent duplicates. We want to be sure we reset that
     // cache when a form is processed, so scenarios that result in
     // the form being built behind the scenes and again for the
     // browser don't increment all the element IDs needlessly.
-    drupal_static_reset('form_clean_id');
+    drupal_static_reset('drupal_css_id');
 
     if ($form_state['submitted'] && !form_get_errors() && !$form_state['rebuild']) {
       // Execute form submit handlers.
@@ -592,7 +592,7 @@ function drupal_prepare_form($form_id, &
   elseif (isset($user->uid) && $user->uid && !$form_state['programmed']) {
     $form['#token'] = $form_id;
     $form['form_token'] = array(
-      '#id' => form_clean_id('edit-' . $form_id . '-form-token'),
+      '#id' => drupal_css_id('edit-' . $form_id . '-form-token'),
       '#type' => 'token',
       '#default_value' => drupal_get_token($form['#token']),
     );
@@ -602,11 +602,11 @@ function drupal_prepare_form($form_id, &
     $form['form_id'] = array(
       '#type' => 'hidden',
       '#value' => $form_id,
-      '#id' => form_clean_id("edit-$form_id"),
+      '#id' => drupal_css_id("edit-$form_id"),
     );
   }
   if (!isset($form['#id'])) {
-    $form['#id'] = form_clean_id($form_id);
+    $form['#id'] = drupal_css_id($form_id);
   }
 
   $form += element_info('form');
@@ -956,7 +956,7 @@ function form_builder($form_id, $element
   }
 
   if (!isset($element['#id'])) {
-    $element['#id'] = form_clean_id('edit-' . implode('-', $element['#parents']));
+    $element['#id'] = drupal_css_id('edit-' . implode('-', $element['#parents']));
   }
   // Handle input elements.
   if (!empty($element['#input'])) {
@@ -1855,7 +1855,7 @@ function form_process_radios($element) {
           '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : NULL,
           '#attributes' => $element['#attributes'],
           '#parents' => $element['#parents'],
-          '#id' => form_clean_id('edit-' . implode('-', $parents_for_id)),
+          '#id' => drupal_css_id('edit-' . implode('-', $parents_for_id)),
           '#ahah' => isset($element['#ahah']) ? $element['#ahah'] : NULL,
         );
       }
@@ -2231,7 +2231,7 @@ function form_process_tableselect($eleme
             '#default_value' => ($element['#default_value'] == $key) ? $key : NULL,
             '#attributes' => $element['#attributes'],
             '#parents' => $element['#parents'],
-            '#id' => form_clean_id('edit-' . implode('-', $parents_for_id)),
+            '#id' => drupal_css_id('edit-' . implode('-', $parents_for_id)),
             '#ahah' => isset($element['#ahah']) ? $element['#ahah'] : NULL,
           );
         }
@@ -2669,42 +2669,6 @@ function _form_set_class(&$element, $cla
 }
 
 /**
- * Prepare an HTML ID attribute string for a form item.
- *
- * Remove invalid characters and guarantee uniqueness.
- *
- * @param $id
- *   The ID to clean.
- * @param $flush
- *   If set to TRUE, the function will flush and reset the static array
- *   which is built to test the uniqueness of element IDs. This is only
- *   used if a form has completed the validation process. This parameter
- *   should never be set to TRUE if this function is being called to
- *   assign an ID to the #ID element.
- * @return
- *   The cleaned ID.
- */
-function form_clean_id($id = NULL) {
-  $seen_ids = &drupal_static(__FUNCTION__, array());
-  $id = str_replace(array('][', '_', ' '), '-', $id);
-
-  // Ensure IDs are unique. The first occurrence is held but left alone.
-  // Subsequent occurrences get a number appended to them. This incrementing
-  // will almost certainly break code that relies on explicit HTML IDs in
-  // forms that appear more than once on the page, but the alternative is
-  // outputting duplicate IDs, which would break JS code and XHTML
-  // validity anyways. For now, it's an acceptable stopgap solution.
-  if (isset($seen_ids[$id])) {
-    $id = $id . '-' . $seen_ids[$id]++;
-  }
-  else {
-    $seen_ids[$id] = 1;
-  }
-
-  return $id;
-}
-
-/**
  * @} End of "defgroup form_api".
  */
 
Index: includes/theme.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.inc,v
retrieving revision 1.497
diff -u -p -r1.497 theme.inc
--- includes/theme.inc	2 Jul 2009 04:27:22 -0000	1.497
+++ includes/theme.inc	7 Jul 2009 09:24:06 -0000
@@ -1974,14 +1974,14 @@ function template_preprocess_page(&$vari
         // more specific data like node-12 or node-edit. To avoid illegal characters in
         // the class, we're removing everything disallowed. We are not using 'a-z' as
         //  that might leave in certain international characters (e.g. German umlauts).
-        $variables['classes_array'][] = preg_replace('![^abcdefghijklmnopqrstuvwxyz0-9-_]+!s', '', form_clean_id(drupal_strtolower($suggestion)));
+        $variables['classes_array'][] = drupal_css_class($suggestion);
       }
     }
   }
 
   // If on an individual node page, add the node type to body classes.
   if (isset($variables['node']) && $variables['node']->type) {
-    $variables['classes_array'][] = 'node-type-' . form_clean_id($variables['node']->type);
+    $variables['classes_array'][] = drupal_css_class('node-type-' . $variables['node']->type);
   }
   // Add information about the number of sidebars.
   if ($variables['layout'] == 'both') {
Index: modules/field/field.form.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.form.inc,v
retrieving revision 1.10
diff -u -p -r1.10 field.form.inc
--- modules/field/field.form.inc	2 Jun 2009 13:47:25 -0000	1.10
+++ modules/field/field.form.inc	7 Jul 2009 09:24:07 -0000
@@ -392,7 +392,7 @@ function field_add_more_js($bundle_name,
   // Just grab the data we need.
   $form_state['values'] = $form_state_copy['values'];
   // Reset cached ids, so that they don't affect the actual form we output.
-  drupal_static_reset('form_clean_id');
+  drupal_static_reset('drupal_css_id');
 
   // Sort the $form_state['values'] we just built *and* the incoming $_POST data
   // according to d-n-d reordering.
Index: modules/filter/filter.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.module,v
retrieving revision 1.262
diff -u -p -r1.262 filter.module
--- modules/filter/filter.module	5 Jul 2009 18:00:08 -0000	1.262
+++ modules/filter/filter.module	7 Jul 2009 09:24:07 -0000
@@ -487,7 +487,7 @@ function filter_form($value = FILTER_FOR
 
   drupal_add_js('misc/form.js');
   drupal_add_css(drupal_get_path('module', 'filter') . '/filter.css');
-  $element_id = form_clean_id('edit-' . implode('-', $parents));
+  $element_id = drupal_css_id('edit-' . implode('-', $parents));
 
   $form = array(
     '#type' => 'fieldset',
Index: modules/simpletest/tests/form_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form_test.module,v
retrieving revision 1.7
diff -u -p -r1.7 form_test.module
--- modules/simpletest/tests/form_test.module	27 May 2009 18:34:00 -0000	1.7
+++ modules/simpletest/tests/form_test.module	7 Jul 2009 09:24:07 -0000
@@ -44,9 +44,9 @@ function form_test_menu() {
     'type' => MENU_CALLBACK,
   );
 
-  $items['form_test/form_clean_id'] = array(
-    'title' => 'form_clean_id test',
-    'page callback' => 'form_test_form_clean_id_page',
+  $items['form_test/drupal_css_id'] = array(
+    'title' => 'drupal_css_id test',
+    'page callback' => 'form_test_drupal_css_id_page',
     'access arguments' => array('access content'),
     'type' => MENU_CALLBACK,
   );
@@ -72,7 +72,7 @@ function form_test_menu() {
 /**
  * Generate a page with three forms, to test the clean_id generation.
  */
-function form_test_form_clean_id_page() {
+function form_test_drupal_css_id_page() {
   $build['form_test_test_form1'] = drupal_get_form('form_test_test_form');
   $build['form_test_test_form2'] = drupal_get_form('form_test_test_form');
   $build['form_test_test_form3'] = drupal_get_form('form_test_test_form');
@@ -86,7 +86,7 @@ function form_test_test_form(&$form_stat
   $form['input'] = array(
     '#type' => 'item',
     '#title' => 'Test Textfield',
-    '#markup' => form_clean_id('form_test_form_clean_id_presence'),
+    '#markup' => drupal_css_id('form_test_drupal_css_id_presence'),
   );
   return $form;
 }
