? .project
? sites/default/db.sqlite
? sites/default/files
? sites/default/settings.php
Index: modules/filter/filter.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.module,v
retrieving revision 1.347
diff -u -p -r1.347 filter.module
--- modules/filter/filter.module	28 Sep 2010 03:30:37 -0000	1.347
+++ modules/filter/filter.module	28 Sep 2010 16:58:32 -0000
@@ -1540,12 +1540,18 @@ function _filter_htmlcorrector($text) {
 }
 
 /**
- * Convert line breaks into <p> and <br> in an intelligent fashion.
- * Based on: http://photomatt.net/scripts/autop
+ * Converts line breaks in text into <p> and <br /> in an intelligent fashion,
+ * preserving existing HTML tags.
+ *
+ * @param $text
+ *   String of text to process.
+ *
+ * @return
+ *   Processed text with paragraph tags inserted.
  */
 function _filter_autop($text) {
-  // All block level tags
-  $block = '(?:table|thead|tfoot|caption|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|select|form|blockquote|address|p|h[1-6]|hr)';
+  $blocklevel = 'body|table|thead|tfoot|caption|col|colgroup|tbody|tr|td|th|div|p|dl|dd|dt|ul|ol|li|pre|select|option|form|map|area|blockquote|address|math|style|p|h[1-6]|hr|fieldset|legend|section|article|aside|hgroup|header|footer|nav|figure|figcaption|details|menu|summary';
+  $parse_state = &drupal_static('_filter_autop', array('p_open' => FALSE, 'tag_stack' => array()));
 
   // Split at opening and closing PRE, SCRIPT, STYLE, OBJECT tags and comments.
   // We don't apply any processing to the contents of these tags to avoid messing
@@ -1576,30 +1582,93 @@ function _filter_autop($text) {
       }
     }
     elseif (!$ignore) {
-      $chunk = preg_replace('|\n*$|', '', $chunk) . "\n\n"; // just to make things a little easier, pad the end
-      $chunk = preg_replace('|<br />\s*<br />|', "\n\n", $chunk);
-      $chunk = preg_replace('!(<' . $block . '[^>]*>)!', "\n$1", $chunk); // Space things out a little
-      $chunk = preg_replace('!(</' . $block . '>)!', "$1\n\n", $chunk); // Space things out a little
-      $chunk = preg_replace("/\n\n+/", "\n\n", $chunk); // take care of duplicates
-      $chunk = preg_replace('/^\n|\n\s*\n$/', '', $chunk);
-      $chunk = '<p>' . preg_replace('/\n\s*\n\n?(.)/', "</p>\n<p>$1", $chunk) . "</p>\n"; // make paragraphs, including one at the end
-      $chunk = preg_replace("|<p>(<li.+?)</p>|", "$1", $chunk); // problem with nested lists
-      $chunk = preg_replace('|<p><blockquote([^>]*)>|i', "<blockquote$1><p>", $chunk);
-      $chunk = str_replace('</blockquote></p>', '</p></blockquote>', $chunk);
-      $chunk = preg_replace('|<p>\s*</p>\n?|', '', $chunk); // under certain strange conditions it could create a P of entirely whitespace
-      $chunk = preg_replace('!<p>\s*(</?' . $block . '[^>]*>)!', "$1", $chunk);
-      $chunk = preg_replace('!(</?' . $block . '[^>]*>)\s*</p>!', "$1", $chunk);
-      $chunk = preg_replace('|(?<!<br />)\s*\n|', "<br />\n", $chunk); // make line breaks
-      $chunk = preg_replace('!(</?' . $block . '[^>]*>)\s*<br />!', "$1", $chunk);
-      $chunk = preg_replace('!<br />(\s*</?(?:p|li|div|th|pre|td|ul|ol)>)!', '$1', $chunk);
-      $chunk = preg_replace('/&([^#])(?![A-Za-z0-9]{1,8};)/', '&amp;$1', $chunk);
+      // Normalize line endings.
+      $chunk = str_replace(array("\r\n", "\r"), "\n", $chunk);
+      // Convert double break tags to double newlines, so they can be
+      // interpreted as paragraphs later.
+      $chunk = preg_replace('~<br />\s*<br />~', "\n\n", $chunk);
+      // Add line breaks inside divs, so paragraphs can start immediately
+      // after a <div> tag.
+      $chunk = preg_replace("~(<div[^>]*>)([^\s])~", "$1\n$2", $chunk);
+      $chunk = preg_replace("~([^\s])(</div[^>]*>)~", "$1\n$2", $chunk);
+      // Send chunks deliminated by whitespace to our callback.
+      $chunk = preg_replace_callback('~.+($|\s+)~', '_filter_autop_callback', $chunk);
+      if ($parse_state['p_open']) {
+        // Close the dangling paragraph tag.
+        $chunk = preg_replace('~(\s*)$~', '</p>$1', $chunk, 1);
+      }
+      // Remove trailing whitespace from inside paragrahs.
+      $chunk = preg_replace('~(\s+)</p>~', '</p>$1', $chunk);
+      // Convert single newlines to <br />.
+      $chunk = preg_replace("~([^\n])\n([^\n])~", "$1<br />\n$2", $chunk);
+      $chunk = preg_replace("~<br />\n(\s*</?(?:" . $blocklevel . "|legend)[^>]*>)~", "\n$1", $chunk);
+      $chunk = preg_replace("~(</?(?:" . $blocklevel . "|legend)[^>]*>\s*)<br />\n~", "$1\n", $chunk);
     }
     $output .= $chunk;
   }
+
+  // Clean up $parse_state for subsequent runs.
+  drupal_static_reset('_filter_autop');
+
   return $output;
 }
 
 /**
+ * Regex replacement callback used internally by _filter_autop().
+ *
+ * @param $matches
+ *   Array of matches, passed by preg_replace_callback().
+ *
+ * @return
+ *   The replacement string.
+ */
+function _filter_autop_callback($matches) {
+  $blocklevel = 'body|table|thead|tfoot|caption|col|colgroup|tbody|tr|td|th|div|p|dl|dd|dt|ul|ol|li|pre|select|option|form|map|area|blockquote|address|math|style|p|h[1-6]|hr|fieldset|legend|section|article|aside|hgroup|header|footer|nav|figure|figcaption|details|menu|summary';
+  $inline = 'a|abbr|acronym|b|basefont|bdo|big|br|cite|code|dfn|em|font|i|img|input|kbd|label|q|s|samp|select|small|span|strike|del|strong|sub|sup|textarea|tt|u|var';
+  $text = $matches[0];
+  $parse_state = &drupal_static('_filter_autop');
+
+  // Keep a persistent tag stack so we can determine what tags we're inside of
+  // from previous passes.
+  preg_match_all('~<(/)?(\w+)( [^/>]*?)?>~', $text, $tag_matches);
+  if ($tag_matches[2]) {
+    foreach ($tag_matches[2] as $i => $tag) {
+      $is_close = (bool) $tag_matches[1][$i];
+      if ($is_close && $tag == end($parse_state['tag_stack'])) {
+        array_pop($parse_state['tag_stack']);
+      }
+      elseif (!$is_close) {
+        array_push($parse_state['tag_stack'], $tag);
+      }
+    }
+  }
+
+  // If no <p> is started and no block level element is imminent, start one.
+  if (!$parse_state['p_open'] && !preg_match('~^</?(' . $blocklevel . ')[^>]*>~', $text)) {
+    $text = '<p>' . $text;
+    $parse_state['p_open'] = TRUE;
+  }
+  // If a <p> is started and a block level element is being closed, close <p>.
+  if ($parse_state['p_open'] && preg_match('~</(' . $blocklevel . ')[^>]*>~', $text)) {
+    $text = preg_replace('~(\s*)?(</(' . $blocklevel . ')[^>]*>)~', '$1</p>$2', $text, 1);
+    $parse_state['p_open'] = FALSE;
+  }
+  // If <p> is started and we have double line breaks, convert to </p><p>.
+  if ($parse_state['p_open'] && preg_match("~\n\s*\n+~", $text)) {
+    // If we're inside an inline tag, we need to use <br /><br /> instead.
+    if (array_intersect($parse_state['tag_stack'], explode('|', $inline))) {
+      $text = preg_replace("~\n(\s*\n+\s*)~", "<br /><br />\n$1", $text);
+    }
+    else {
+      $text = preg_replace("~\n(\s*\n+\s*)~", "</p>\n$1", $text);
+      $parse_state['p_open'] = FALSE;
+    }
+  }
+
+  return $text;
+}
+
+/**
  * Filter tips callback for auto-paragraph filter.
  */
 function _filter_autop_tips($filter, $format, $long = FALSE) {
