You are here

function _mailsystem_html_to_text_table in Mail System 7.3

Same name and namespace in other branches
  1. 8.2 html_to_text.inc \_mailsystem_html_to_text_table()
  2. 6.2 html_to_text.inc \_mailsystem_html_to_text_table()
  3. 7.2 html_to_text.inc \_mailsystem_html_to_text_table()

Helper function for _mailsystem_html_to_text().

Renders a <table> DOM Node into plain text. Attributes such as rowspan, colspan, padding, border, etc. are ignored.

Parameters

DOMNode $node: The DOMNode corresponding to the <table> tag and its contents.

array $allowed_tags: The list of allowed tags passed to _mailsystem_html_to_text().

array &$notes: A writeable array of footnote reference numbers, keyed by their respective hyperlink destination urls.

int $table_width: The desired maximum table width, after word-wrapping each table cell.

Return value

string A plain text representation of the table.

See also

_mailsystem_html_to_text()

1 call to _mailsystem_html_to_text_table()
_mailsystem_html_to_text in ./html_to_text.inc
Helper function for drupal_html_to_text().

File

./html_to_text.inc, line 481
Copy of drupal_html_to_text improvements from issue #299138.

Code

function _mailsystem_html_to_text_table(DOMNode $node, array $allowed_tags = NULL, array &$notes = array(), $table_width = 80) {
  $eol = variable_get('mail_line_endings', MAIL_LINE_ENDINGS);
  $header = array();
  $footer = array();
  $body = array();
  $text = $eol;
  $current = $node;
  while (TRUE) {
    if (isset($current->tagName)) {
      switch ($current->tagName) {

        // The table caption is added first.
        case 'caption':
          $text = _mailsystem_html_to_text($current, $allowed_tags, $notes, $table_width);
          break;
        case 'tr':
          switch ($current->parentNode->tagName) {
            case 'thead':
              $header[] = $current;
              break;
            case 'tfoot':
              $footer[] = $current;
              break;

            // Either 'tbody' or 'table'.
            default:
              $body[] = $current;
              break;
          }
          break;
        default:
          if ($current
            ->hasChildNodes()) {
            $current = $current->firstChild;
            continue 2;
          }
      }
    }
    do {
      if ($current->nextSibling) {
        $current = $current->nextSibling;
        continue 2;
      }
      $current = $current->parentNode;
    } while ($current && !$current
      ->isSameNode($node));
    break;
  }

  // Merge the thead, tbody, and tfoot sections together.
  if ($rows = array_merge($header, $body, $footer)) {
    $num_rows = count($rows);

    // First just count the number of columns.
    $num_cols = 0;
    foreach ($rows as $row) {
      $row_cols = 0;
      foreach ($row->childNodes as $cell) {
        if (isset($cell->tagName) && in_array($cell->tagName, array(
          'td',
          'th',
        ))) {
          $row_cols++;
        }
      }
      $num_cols = max($num_cols, $row_cols);
    }

    // If any columns were found, calculate each column height and width.
    if ($num_cols) {

      // Set up a binary search for best wrap width for each column.
      $max = max($table_width - $num_cols - 1, 1);
      $max_wraps = array_fill(0, $num_cols, $max);
      $try = max(intval(($table_width - 1) / $num_cols - 1), 1);
      $try_wraps = array_fill(0, $num_cols, $try);
      $min_wraps = array_fill(0, $num_cols, 1);

      // Start searching...
      $change = FALSE;
      do {
        $change = FALSE;
        $widths = array_fill(0, $num_cols, 0);
        $heights = array_fill(0, $num_rows, 0);
        $table = array_fill(0, $num_rows, array_fill(0, $num_cols, ''));
        $breaks = array_fill(0, $num_cols, FALSE);
        foreach ($rows as $i => $row) {
          $j = 0;
          foreach ($row->childNodes as $cell) {
            if (!isset($cell->tagName) || !in_array($cell->tagName, array(
              'td',
              'th',
            ))) {

              // Skip text nodes.
              continue;
            }

            // Render the cell contents.
            $cell = _mailsystem_html_to_text($cell, $allowed_tags, $notes, $try_wraps[$j]);

            // Trim leading line-breaks and trailing whitespace.
            // chr(160) is the non-breaking space character.
            $cell = rtrim(ltrim($cell, $eol), ' ' . $eol . chr(160));
            $table[$i][$j] = $cell;
            if ($cell > '') {

              // Split the cell into lines.
              $lines = explode($eol, $cell);

              // The row height is the maximum number of lines among all the
              // cells in that row.
              $heights[$i] = max($heights[$i], count($lines));
              foreach ($lines as $line) {
                $this_width = drupal_strlen($line);

                // The column width is the maximum line width among all the
                // lines in that column.
                if ($this_width > $widths[$j]) {
                  $widths[$j] = $this_width;

                  // If the longest line in a column contains at least one
                  // space character, then the table can be made narrower.
                  $breaks[$j] = strpos(' ', $line) !== FALSE;
                }
              }
            }
            $j++;
          }
        }

        // Calculate the total table width;.
        $this_width = array_sum($widths) + $num_cols + 1;
        if ($this_width > $table_width) {

          // Wider than desired.
          if (!in_array(TRUE, $breaks)) {

            // If there are no more break points, then the table is already as
            // narrow as it can get, so we're done.
            break;
          }
          foreach ($try_wraps as $i => $wrap) {
            $max_wraps[$i] = min($max_wraps[$i], $wrap);
            if ($breaks[$i]) {
              $new_wrap = intval(($min_wraps[$i] + $max_wraps[$i]) / 2);
              $new_wrap = min($new_wrap, $widths[$i] - 1);
              $new_wrap = max($new_wrap, $min_wraps[$i]);
            }
            else {

              // There's no point in trying to make the column narrower than
              // the widest un-wrappable line in the column.
              $min_wraps[$i] = $widths[$i];
              $new_wrap = $widths[$i];
            }
            if ($try_wraps[$i] > $new_wrap) {
              $try_wraps[$i] = $new_wrap;
              $change = TRUE;
            }
          }
        }
        elseif ($this_width < $table_width) {

          // Narrower than desired.
          foreach ($try_wraps as $i => $wrap) {
            if ($min_wraps[$i] < $wrap) {
              $min_wraps[$i] = $wrap;
            }
            $new_wrap = intval(($min_wraps[$i] + $max_wraps[$i]) / 2);
            $new_wrap = max($new_wrap, $widths[$i] + 1);
            $new_wrap = min($new_wrap, $max_wraps[$i]);
            if ($try_wraps[$i] < $new_wrap) {
              $try_wraps[$i] = $new_wrap;
              $change = TRUE;
            }
          }
        }
      } while ($change);

      // Pad each cell to column width and line height.
      for ($i = 0; $i < $num_rows; $i++) {
        if ($heights[$i]) {
          for ($j = 0; $j < $num_cols; $j++) {
            $cell = $table[$i][$j];

            // Pad each cell to the maximum number of lines in that row.
            $lines = array_pad(explode($eol, $cell), $heights[$i], '');
            foreach ($lines as $k => $line) {

              // Pad each line to the maximum width in that column.
              $repeat = $widths[$j] - drupal_strlen($line);
              if ($repeat > 0) {

                // chr(160) is the non-breaking space character.
                $lines[$k] .= str_repeat(chr(160), $repeat);
              }
            }
            $table[$i][$j] = $lines;
          }
        }
      }

      // Generate the row separator line.
      $separator = '+';
      for ($i = 0; $i < $num_cols; $i++) {
        $separator .= str_repeat('-', $widths[$i]) . '+';
      }
      $separator .= $eol;
      for ($i = 0; $i < $num_rows; $i++) {
        $text .= $separator;
        if (!$heights[$i]) {
          continue;
        }
        $row = $table[$i];

        // For each row, iterate first by lines within the row.
        for ($k = 0; $k < $heights[$i]; $k++) {

          // Add a vertical-bar at the beginning of each row line.
          $row_line = '|';
          $trimmed = '';

          // Within each row line, iterate by cells within that line.
          for ($j = 0; $j < $num_cols; $j++) {

            // Add a vertical bar at the end of each cell line.
            $row_line .= $row[$j][$k] . '|';

            // chr(160) is the non-breaking space character.
            $trimmed .= trim($row[$j][$k], ' ' . $eol . chr(160));
          }
          if ($trimmed > '') {

            // Only print rows that are non-empty.
            $text .= $row_line . $eol;
          }
        }
      }

      // Final output ends with a row separator.
      $text .= $separator;
    }
  }

  // Make sure formatted table content doesn't line-wrap.
  // chr(160) is the non-breaking space character.
  return str_replace(' ', chr(160), $text);
}