protected function outputTag(Tag $tag) { $this->isRich = \true; $tagName = $tag->getName(); $tagPos = $tag->getPos(); $tagLen = $tag->getLen(); $tagFlags = $tag->getFlags(); if ($tagFlags & self::RULE_IGNORE_WHITESPACE) { $skipBefore = 1; $skipAfter = $tag->isEndTag() ? 2 : 1; } else { $skipBefore = $skipAfter = 0; } $closeParagraph = \false; if ($tag->isStartTag()) { if ($tagFlags & self::RULE_BREAK_PARAGRAPH) { $closeParagraph = \true; } } else { $closeParagraph = \true; } $this->outputText($tagPos, $skipBefore, $closeParagraph); $tagText = $tagLen ? \htmlspecialchars(\substr($this->text, $tagPos, $tagLen), \ENT_NOQUOTES, 'UTF-8') : ''; if ($tag->isStartTag()) { if (!($tagFlags & self::RULE_BREAK_PARAGRAPH)) { $this->outputParagraphStart($tagPos); } $colonPos = \strpos($tagName, ':'); if ($colonPos) { $this->namespaces[\substr($tagName, 0, $colonPos)] = 0; } $this->output .= '<' . $tagName; $attributes = $tag->getAttributes(); \ksort($attributes); foreach ($attributes as $attrName => $attrValue) { $this->output .= ' ' . $attrName . '="' . \str_replace("\n", ' ', \htmlspecialchars($attrValue, \ENT_COMPAT, 'UTF-8')) . '"'; } if ($tag->isSelfClosingTag()) { if ($tagLen) { $this->output .= '>' . $tagText . '</' . $tagName . '>'; } else { $this->output .= '/>'; } } elseif ($tagLen) { $this->output .= '><s>' . $tagText . '</s>'; } else { $this->output .= '>'; } } else { if ($tagLen) { $this->output .= '<e>' . $tagText . '</e>'; } $this->output .= '</' . $tagName . '>'; } $this->pos = $tagPos + $tagLen; $this->wsPos = $this->pos; while ($skipAfter && $this->wsPos < $this->textLen && $this->text[$this->wsPos] === "\n") { --$skipAfter; ++$this->wsPos; } }
/** * @testdox isStartTag() returns true if the tag's type is Tag::SELF_CLOSING_TAG */ public function testIsStartTagSelfClosing() { $tag = new Tag(Tag::SELF_CLOSING_TAG, 'X', 12, 34); $this->assertTrue($tag->isStartTag()); }
/** * Append a tag to the output * * @param Tag $tag Tag to append * @return void */ protected function outputTag(Tag $tag) { $this->isRich = true; $tagName = $tag->getName(); $tagPos = $tag->getPos(); $tagLen = $tag->getLen(); $tagFlags = $tag->getFlags(); if ($tagFlags & self::RULE_IGNORE_WHITESPACE) { $skipBefore = 1; $skipAfter = $tag->isEndTag() ? 2 : 1; } else { $skipBefore = $skipAfter = 0; } // Current paragraph must end before the tag if: // - the tag is a start (or self-closing) tag and it breaks paragraphs, or // - the tag is an end tag (but not self-closing) $closeParagraph = false; if ($tag->isStartTag()) { if ($tagFlags & self::RULE_BREAK_PARAGRAPH) { $closeParagraph = true; } } else { $closeParagraph = true; } // Let the cursor catch up with this tag's position $this->outputText($tagPos, $skipBefore, $closeParagraph); // Capture the text consumed by the tag $tagText = $tagLen ? htmlspecialchars(substr($this->text, $tagPos, $tagLen), ENT_NOQUOTES, 'UTF-8') : ''; // Output current tag if ($tag->isStartTag()) { // Handle paragraphs before opening the tag if (!($tagFlags & self::RULE_BREAK_PARAGRAPH)) { $this->outputParagraphStart($tagPos); } // Record this tag's namespace, if applicable $colonPos = strpos($tagName, ':'); if ($colonPos) { $this->namespaces[substr($tagName, 0, $colonPos)] = 0; } // Open the start tag and add its attributes, but don't close the tag $this->output .= '<' . $tagName; // We output the attributes in lexical order. Helps canonicalizing the output and could // prove useful someday $attributes = $tag->getAttributes(); ksort($attributes); foreach ($attributes as $attrName => $attrValue) { $this->output .= ' ' . $attrName . '="' . htmlspecialchars($attrValue, ENT_COMPAT, 'UTF-8') . '"'; } if ($tag->isSelfClosingTag()) { if ($tagLen) { $this->output .= '>' . $tagText . '</' . $tagName . '>'; } else { $this->output .= '/>'; } } elseif ($tagLen) { $this->output .= '><s>' . $tagText . '</s>'; } else { $this->output .= '>'; } } else { if ($tagLen) { $this->output .= '<e>' . $tagText . '</e>'; } $this->output .= '</' . $tagName . '>'; } // Move the cursor past the tag $this->pos = $tagPos + $tagLen; // Skip newlines (no other whitespace) after this tag $this->wsPos = $this->pos; while ($skipAfter && $this->wsPos < $this->textLen && $this->text[$this->wsPos] === "\n") { // Decrement the number of lines to skip --$skipAfter; // Move the cursor past the newline ++$this->wsPos; } }