You are here

function XBBCodeFilter::process in Extensible BBCode 8

Execute the filter on a particular text.

Note: This function makes use of substr() and strlen() instead of Drupal wrappers. This is the correct approach as all offsets are calculated by the PREG_OFFSET_CAPTURE setting of preg_match_all(), which returns byte offsets rather than character offsets.

Parameters

$text: The text to be filtered.

Return value

HTML code.

File

lib/Drupal/xbbcode/XBBCodeFilter.php, line 49
Definition of Drupal\xbbcode\XBBCodeFilter.

Class

XBBCodeFilter
The filtering class. This will be instanced for each filter, and then called to process a piece of text.

Namespace

Drupal\xbbcode

Code

function process($text) {

  // Find all opening and closing tags in the text.
  preg_match_all(XBBCODE_RE_TAG, $text, $tags, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
  if (!$tags) {
    return $text;
  }

  // Initialize the stack with a root tag, and the name tracker.
  $stack = array(
    new XBBCodeRootElement(),
  );
  $open_by_name = array();
  foreach ($tags as $i => $tag) {
    $tag = $tags[$i] = new XBBCodeTagMatch($tag);
    $open_by_name[$tag->name] = 0;
  }
  foreach ($tags as $tag) {

    // Case 1: The tag is opening, and known to the filter.
    if (!$tag->closing && isset($this->tags[$tag->name])) {

      // Add text before the new tag to the parent, then stack the new tag.
      end($stack)
        ->advance($text, $tag->start);

      // Stack the newly opened tag, or render it if it's selfclosing.
      if ($this->tags[$tag->name]->options->selfclosing) {
        $rendered = $this
          ->render_tag($tag);
        if ($rendered === NULL) {
          $rendered = $tag->element;
        }
        end($stack)
          ->append($rendered, $tag->end);
      }
      else {
        array_push($stack, $tag);
        $open_by_name[$tag->name]++;
      }
    }
    elseif ($tag->closing && !empty($open_by_name[$tag->name])) {

      // Find the last matching opening tag, breaking any unclosed tag since then.
      while (end($stack)->name != $tag->name) {
        $dangling = array_pop($stack);
        end($stack)
          ->break_tag($dangling);
        $open_by_name[$dangling->name]--;
      }
      end($stack)
        ->advance($text, $tag->start);
      $open_by_name[$tag->name]--;

      // If the tag forbids rendering its content, revert to the unrendered text.
      if ($this->tags[$tag->name]->options->nocode) {
        end($stack)
          ->revert($text);
      }
      if ($this->tags[$tag->name]->options->plain) {

        // We will double-encode entities only if non-encoded chars exist.
        if (end($stack)->content != htmlspecialchars(end($stack)->content, ENT_QUOTES, 'UTF-8', FALSE)) {
          end($stack)->content = check_plain(end($stack)->content);
        }
      }

      // Append the rendered HTML to the content of its parent tag.
      $current = array_pop($stack);
      $rendered = $this
        ->render_tag($current);
      if ($rendered === NULL) {
        $rendered = $current->element . $current->content . $tag->element;
      }
      end($stack)
        ->append($rendered, $tag->end);
    }
  }
  end($stack)->content .= substr($text, end($stack)->offset);
  if ($this->autoclose_tags) {
    while (count($stack) > 1) {

      // Render the unclosed tag and pop it off the stack
      $output = $this
        ->render_tag(array_pop($stack));
      end($stack)->content .= $output;
    }
  }
  else {
    while (count($stack) > 1) {
      $current = array_pop($stack);
      $content = $current->element . $current->content;
      end($stack)->content .= $content;
    }
  }
  return end($stack)->content;
}