From 96a378081b687af2fc9a42dd3b7e816c9783b7d6 Mon Sep 17 00:00:00 2001
From: Bob Vincent
Date: Tue, 15 Mar 2011 06:26:45 -0400
Subject: [PATCH] Improved text_summary() function.
---
modules/field/modules/text/text.module | 181 ++++++++++++++++++++++----------
1 files changed, 125 insertions(+), 56 deletions(-)
diff --git a/modules/field/modules/text/text.module b/modules/field/modules/text/text.module
index 89c605c..fbef11f 100644
--- a/modules/field/modules/text/text.module
+++ b/modules/field/modules/text/text.module
@@ -327,6 +327,10 @@ function _text_sanitize($instance, $langcode, $item, $column) {
* place such as the end of a paragraph, a line break, or the end of a
* sentence (in that order of preference).
*
+ * @note
+ * This function uses strlen(), strpos(), etc. rather than their multibyte
+ * equivalents where doing so increases speed without affecting output.
+ *
* @param $text
* The content for which a summary will be generated.
* @param $format
@@ -344,7 +348,7 @@ function _text_sanitize($instance, $langcode, $item, $column) {
* @return
* The generated summary.
*/
-function text_summary($text, $format = NULL, $size = NULL) {
+function text_summary($text, $format = NULL, $size = NULL, &$summary_length = NULL) {
if (!isset($size)) {
// What used to be called 'teaser' is now called 'summary', but
@@ -368,6 +372,7 @@ function text_summary($text, $format = NULL, $size = NULL) {
// We check for the presence of the PHP evaluator filter in the current
// format. If the body contains PHP code, we do not split it up to prevent
// parse errors.
+ $filters = array();
if (isset($format)) {
$filters = filter_list_format($format);
if (isset($filters['php_code']) && $filters['php_code']->status && strpos($text, '') !== FALSE) {
@@ -380,67 +385,131 @@ function text_summary($text, $format = NULL, $size = NULL) {
return $text;
}
- // If the delimiter has not been specified, try to split at paragraph or
- // sentence boundaries.
-
- // The summary may not be longer than maximum length specified. Initial slice.
- $summary = truncate_utf8($text, $size);
-
- // Store the actual length of the UTF8 string -- which might not be the same
- // as $size.
- $max_rpos = strlen($summary);
-
- // How much to cut off the end of the summary so that it doesn't end in the
- // middle of a paragraph, sentence, or word.
- // Initialize it to maximum in order to find the minimum.
- $min_rpos = $max_rpos;
-
- // Store the reverse of the summary. We use strpos on the reversed needle and
- // haystack for speed and convenience.
- $reversed = strrev($summary);
-
- // Build an array of arrays of break points grouped by preference.
- $break_points = array();
-
- // A paragraph near the end of sliced summary is most preferable.
- $break_points[] = array('
' => 0);
-
- // If no complete paragraph then treat line breaks as paragraphs.
- $line_breaks = array('
' => 6, '
' => 4);
- // Newline only indicates a line break if line break converter
- // filter is present.
- if (isset($filters['filter_autop'])) {
- $line_breaks["\n"] = 1;
- }
- $break_points[] = $line_breaks;
-
- // If the first paragraph is too long, split at the end of a sentence.
- $break_points[] = array('. ' => 1, '! ' => 1, '? ' => 1, '。' => 0, '؟ ' => 1);
-
- // Iterate over the groups of break points until a break point is found.
- foreach ($break_points as $points) {
- // Look for each break point, starting at the end of the summary.
- foreach ($points as $point => $offset) {
- // The summary is already reversed, but the break point isn't.
- $rpos = strpos($reversed, strrev($point));
- if ($rpos !== FALSE) {
- $min_rpos = min($rpos + $offset, $min_rpos);
+ $filter_newline = isset($filters['filter/1']);
+ $body_length = strlen(body);
+
+ $position = 0;
+ $length = 0;
+ $stack = array();
+ while ($position < $body_length && $length < $size) {
+ $last_tag = FALSE;
+ $offset = strpos($body, '<', $position);
+ if ($offset === FALSE) {
+ // There are no more tags, so find the UTF-8 string length.
+ $additional = drupal_strlen(substr($body, $position, $body_length - $position));
+ $num_chars = $body_length;
+ }
+ else {
+ // Count UTF-8 characters between the previous position and the next tag.
+ $additional = drupal_strlen(substr($body, $position, $offset - $position));
+ ++$offset; // Skip the '<' character.
+ $num_chars = strpos($body, '>', $offset);
+ if ($body[$offset] == '/') {
+ // Found a closing tag, so pop the opening tag too.
+ array_pop($stack);
+ }
+ elseif ($body[$num_chars - 1] != '/') { // Skip empty tags.
+ // Found an opening tag; save it on the stack.
+ $end_name = strpos($body, ' ', $offset);
+ if ($end_name === FALSE || $end_name > $num_chars) {
+ $end_name = $num_chars;
+ }
+ $tag_name = substr($body, $offset, $end_name - $offset);
+ switch ($tag_name) { // Ignore empty tags that were not properly closed.
+ case 'br':
+ case 'hr':
+ case 'img':
+ case 'input':
+ break;
+ default:
+ $stack[] = $tag_name;
+ $last_tag = TRUE;
+ break;
+ }
+ }
+ // For now, we assume properly opening/closing tag boundaries.
+ if ($num_chars === FALSE) {
+ // Either the last tag was not closed or it wasn't a tag.
+ $num_chars = $body_length;
+ }
+ else {
+ ++$num_chars; // Skip the '>' character.
}
}
+ // Are there any characters to add the to result?
+ if ($additional) {
+ if ($length + $additional >= $size) {
+ // The last tag did not make it in.
+ if ($last_tag) {
+ array_pop($stack);
+ }
+ // There are too many characters, so search for a break-point.
+ $offset = $position + $size - $length;
+ if ($body[$offset] != ' ') while ($offset > $position) {
+ switch ($body[$offset - 1]) {
+ case "\xD8":
+ // Is this the Arabic equivalent of the ascii '?' character?
+ if (!isset($body[$offset]) || $body[$offset] != "\x9F") {
+ // No; this is not the right sequence.
+ break;
+ }
+ if ($offset + 1 == $body_length || $body[$offset + 1] == ' ') {
+ // Found a break-point.
+ break 2;
+ }
+ if ($body[$offset + 1] == '"') {
+ $offset += 2;
+ break 2;
+ }
+ break;
+ case '.':
+ case '!':
+ case '?':
+ if ($offset == $body_length || $body[$offset] == ' ') {
+ // Found a break-point.
+ break 2;
+ }
+ if ($body[$offset] == '"') {
+ ++$offset;
+ break 2;
+ }
+ break;
+ case "\n":
+ if (!$filter_newline) {
+ break;
+ }
+ case ' ':
+ // Found the (breaking) space; remove and break there.
+ --$offset;
+ break 2;
+ // @todo Add support for other UTF-8 spaces?
+ case "\xE3":
+ // Found the CJK ideographic full stop?
+ if (isset($body[$offset + 1]) && $body[$offset] == "\x80" && $body[$offset + 1] == "\x82") {
+ // keep this character in full
+ $offset += 2;
+ break 2;
+ }
+ break;
- // If a break point was found in this group, slice and stop searching.
- if ($min_rpos !== $max_rpos) {
- // Don't slice with length 0. Length must be <0 to slice from RHS.
- $summary = ($min_rpos === 0) ? $summary : substr($summary, 0, 0 - $min_rpos);
- break;
+ }
+ --$offset;
+ }
+ $position = $offset;
+ break;
+ }
+ $length += $additional;
}
+ $position = $num_chars;
}
-
- // If the htmlcorrector filter is present, apply it to the generated summary.
- if (isset($filters['filter_htmlcorrector'])) {
- $summary = _filter_htmlcorrector($summary);
+ $summary = substr($body, 0, $position);
+ if (!empty($stack)) {
+ // If closing tags are missing, we add an ellipsis and closing tags.
+ $summary .= t('...');
+ do {
+ $summary .= '' . array_pop($stack) . '>';
+ } while (!empty($stack));
}
-
return $summary;
}
--
1.7.1