This directive turns on auto-paragraphing, where double newlines are converted in to paragraphs whenever possible. Auto-paragraphing applies when:
p
tags must be allowed for this directive to take effect.
We do not use br
tags for paragraphing, as that is
semantically incorrect.
This directive has been available since 2.0.1.
'); /** * Injector that auto paragraphs text in the root node based on * double-spacing. */ class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector { var $name = 'AutoParagraph'; var $needed = array('p'); function _pStart() { $par = new HTMLPurifier_Token_Start('p'); $par->armor['MakeWellFormed_TagClosedError'] = true; return $par; } function handleText(&$token) { $text = $token->data; if (empty($this->currentNesting)) { if (!$this->allowsElement('p')) return; // case 1: we're in root node (and it allows paragraphs) $token = array($this->_pStart()); $this->_splitText($text, $token); } elseif ($this->currentNesting[count($this->currentNesting)-1]->name == 'p') { // case 2: we're in a paragraph $token = array(); $this->_splitText($text, $token); } elseif ($this->allowsElement('p')) { // case 3: we're in an element that allows paragraphs if (strpos($text, "\n\n") !== false) { // case 3.1: this text node has a double-newline $token = array($this->_pStart()); $this->_splitText($text, $token); } else { $ok = false; // test if up-coming tokens are either block or have // a double newline in them for ($i = $this->inputIndex + 1; isset($this->inputTokens[$i]); $i++) { if ($this->inputTokens[$i]->type == 'start'){ if (!$this->_isInline($this->inputTokens[$i])) { $ok = true; } break; } if ($this->inputTokens[$i]->type == 'end') break; if ($this->inputTokens[$i]->type == 'text') { if (strpos($this->inputTokens[$i]->data, "\n\n") !== false) { $ok = true; } if (!$this->inputTokens[$i]->is_whitespace) break; } } if ($ok) { // case 3.2: this text node is next to another node // that will start a paragraph $token = array($this->_pStart(), $token); } } } } function handleStart(&$token) { // check if we're inside a tag already if (!empty($this->currentNesting)) { if ($this->allowsElement('p')) { // special case: we're in an element that allows paragraphs // this token is already paragraph, abort if ($token->name == 'p') return; // check if this token is adjacent to the parent if ($this->inputTokens[$this->inputIndex - 1]->type != 'start') { // not adjacent, we can abort early // add lead paragraph tag if our token is inline if ($this->_isInline($token)) { $token = array($this->_pStart(), $token); } return; } // this token is the first child of the element that allows // paragraph. We have to peek ahead and see whether or not // there is anything inside that suggests that a paragraph // will be needed $ok = false; // maintain a mini-nesting counter, this lets us bail out // early if possible $j = 2; // current nesting, is two due to parent and this start for ($i = $this->inputIndex + 1; isset($this->inputTokens[$i]); $i++) { if ($this->inputTokens[$i]->type == 'start') $j++; if ($this->inputTokens[$i]->type == 'end') $j--; if ($this->inputTokens[$i]->type == 'text') { if (strpos($this->inputTokens[$i]->data, "\n\n") !== false) { $ok = true; break; } } if ($j <= 0) break; } if ($ok) { $token = array($this->_pStart(), $token); } } return; } // check if the start tag counts as a "block" element if (!$this->_isInline($token)) return; // append a paragraph tag before the token $token = array($this->_pStart(), $token); } /** * Splits up a text in paragraph tokens and appends them * to the result stream that will replace the original * @param $data String text data that will be processed * into paragraphs * @param $result Reference to array of tokens that the * tags will be appended onto * @param $config Instance of HTMLPurifier_Config * @param $context Instance of HTMLPurifier_Context * @private */ function _splitText($data, &$result) { $raw_paragraphs = explode("\n\n", $data); // remove empty paragraphs $paragraphs = array(); $needs_start = false; $needs_end = false; for ($i = 0, $c = count($raw_paragraphs); $i < $c; $i++) { $par = $raw_paragraphs[$i]; if (trim($par) !== '') { $paragraphs[] = $par; continue; } if ($i == 0 && empty($result)) { // The empty result indicates that the AutoParagraph // injector did not add any start paragraph tokens. // The fact that the first paragraph is empty indicates // that there was a double-newline at the start of the // data. // Combined together, this means that we are in a paragraph, // and the newline means we should start a new one. $result[] = new HTMLPurifier_Token_End('p'); // However, the start token should only be added if // there is more processing to be done (i.e. there are // real paragraphs in here). If there are none, the // next start paragraph tag will be handled by the // next run-around the injector $needs_start = true; } elseif ($i + 1 == $c) { // a double-paragraph at the end indicates that // there is an overriding need to start a new paragraph // for the next section. This has no effect until // we've processed all of the other paragraphs though $needs_end = true; } } // check if there are no "real" paragraphs to be processed if (empty($paragraphs)) { return; } // add a start tag if an end tag was added while processing // the raw paragraphs (that happens if there's a leading double // newline) if ($needs_start) $result[] = $this->_pStart(); // append the paragraphs onto the result foreach ($paragraphs as $par) { $result[] = new HTMLPurifier_Token_Text($par); $result[] = new HTMLPurifier_Token_End('p'); $result[] = $this->_pStart(); } // remove trailing start token, if one is needed, it will // be handled the next time this injector is called array_pop($result); // check the outside to determine whether or not the // end paragraph tag should be removed. It should be removed // unless the next non-whitespace token is a paragraph // or a block element. $remove_paragraph_end = true; if (!$needs_end) { // Start of the checks one after the current token's index for ($i = $this->inputIndex + 1; isset($this->inputTokens[$i]); $i++) { if ($this->inputTokens[$i]->type == 'start' || $this->inputTokens[$i]->type == 'empty') { $remove_paragraph_end = $this->_isInline($this->inputTokens[$i]); } // check if we can abort early (whitespace means we carry-on!) if ($this->inputTokens[$i]->type == 'text' && !$this->inputTokens[$i]->is_whitespace) break; // end tags will automatically be handled by MakeWellFormed, // so we don't have to worry about them if ($this->inputTokens[$i]->type == 'end') break; } } else { $remove_paragraph_end = false; } // check the outside to determine whether or not the // end paragraph tag should be removed if ($remove_paragraph_end) { array_pop($result); } } /** * Returns true if passed token is inline (and, ergo, allowed in * paragraph tags) * @private */ function _isInline($token) { return isset($this->htmlDefinition->info['p']->child->elements[$token->name]); } }