// tags that will eventually be removed within the marker processor
$this->buildXMLStructure();
+ $this->handleSourceBBCodes();
+
$this->buildParsedString();
return $this->parsedText;
}
+ /**
+ * @inheritDoc
+ */
+ public function buildXMLStructure() {
+ // stack for open tags
+ $openTagStack = $openTagDataStack = [];
+ $newTagArray = [];
+ $newTextArray = [];
+
+ $i = -1;
+ foreach ($this->tagArray as $i => $tag) {
+ if ($tag['closing']) {
+ // closing tag
+ if (in_array($tag['name'], $openTagStack) && $this->isAllowed($openTagStack, $tag['name'], true)) {
+ // close unclosed tags
+ while (($previousTag = end($openTagStack)) != $tag['name']) {
+ $nextIndex = count($newTagArray);
+
+ // mark as invalid and do not flag as opened tag
+ $newTag = $this->buildTag('[/'.end($openTagStack).']');
+ $newTag['invalid'] = true;
+
+ $newTagArray[$nextIndex] = $newTag;
+ if (!isset($newTextArray[$nextIndex])) $newTextArray[$nextIndex] = '';
+ $newTextArray[$nextIndex] .= $this->textArray[$i];
+ $this->textArray[$i] = '';
+ array_pop($openTagStack);
+ array_pop($openTagDataStack);
+ }
+
+ $nextIndex = count($newTagArray);
+ $newTagArray[$nextIndex] = $tag;
+ array_pop($openTagStack);
+ array_pop($openTagDataStack);
+ if (!isset($newTextArray[$nextIndex])) $newTextArray[$nextIndex] = '';
+ $newTextArray[$nextIndex] .= $this->textArray[$i];
+ }
+ else {
+ // no such tag open
+ // handle as plain text
+ $this->textArray[$i] .= $tag['source'];
+ $last = count($newTagArray);
+ if (!isset($newTextArray[$last])) $newTextArray[$last] = '';
+ $newTextArray[$last] .= $this->textArray[$i];
+ }
+ }
+ else {
+ // opening tag
+ if ($this->isAllowed($openTagStack, $tag['name']) && $this->isValidTag($tag)) {
+ $openTagStack[] = $tag['name'];
+ $openTagDataStack[] = $tag;
+ $nextIndex = count($newTagArray);
+ $newTagArray[$nextIndex] = $tag;
+ if (!isset($newTextArray[$nextIndex])) $newTextArray[$nextIndex] = '';
+ $newTextArray[$nextIndex] .= $this->textArray[$i];
+ }
+ else {
+ // tag not allowed
+ $this->textArray[$i] .= $tag['source'];
+ $last = count($newTagArray);
+ if (!isset($newTextArray[$last])) $newTextArray[$last] = '';
+ $newTextArray[$last] .= $this->textArray[$i];
+ }
+ }
+ }
+
+ $last = count($newTagArray);
+ if (!isset($newTextArray[$last])) $newTextArray[$last] = '';
+ $newTextArray[$last] .= $this->textArray[$i + 1];
+
+ // close unclosed open tags
+ while (end($openTagStack)) {
+ $nextIndex = count($newTagArray);
+
+ // mark as invalid
+ $newTag = $this->buildTag('[/'.end($openTagStack).']');
+ $newTag['invalid'] = true;
+
+ $newTagArray[$nextIndex] = $newTag;
+ if (!isset($newTextArray[$nextIndex])) $newTextArray[$nextIndex] = '';
+ array_pop($openTagStack);
+ array_pop($openTagDataStack);
+ }
+
+ $this->tagArray = $newTagArray;
+ $this->textArray = $newTextArray;
+ }
+
+ /**
+ * Flags bbcodes inside code bbcodes for reversal, turning them back
+ * into their source state (= textual representation).
+ */
+ protected function handleSourceBBCodes() {
+ $sourceBBCodes = $this->getSourceBBCodes();
+
+ $inCode = '';
+ $openTagStack = [];
+
+ for ($i = 0, $length = count($this->tagArray); $i < $length; $i++) {
+ $tag = $this->tagArray[$i];
+
+ if (!empty($tag['invalid'])) {
+ continue;
+ }
+
+ $name = $tag['name'];
+
+ if ($tag['closing']) {
+ if ($inCode) {
+ // matches opening code tag
+ if ($inCode === $name) {
+ $inCode = '';
+ array_pop($openTagStack);
+ }
+ else {
+ // unrelated tag, flag as invalid
+ $this->tagArray[$i]['inCode'] = true;
+ }
+
+ continue;
+ }
+
+ array_pop($openTagStack);
+ }
+ else {
+ if ($inCode) {
+ // inside code block, flag as invalid
+ $this->tagArray[$i]['inCode'] = true;
+ continue;
+ }
+
+ // starts a new code block
+ if (in_array($name, $sourceBBCodes)) {
+ // look ahead to see if there is a valid closing tag
+ $hasClosingTag = false;
+ for ($j = $i + 1; $j < $length; $j++) {
+ if ($this->tagArray[$j]['name'] === $name && empty($this->tagArray[$j]['invalid'])) {
+ $hasClosingTag = true;
+ break;
+ }
+ }
+
+ if ($hasClosingTag) {
+ $inCode = $name;
+ }
+ else {
+ // no closing tag, flag as invalid to avoid the
+ // entire content afterwards being treated as code
+ $this->tagArray[$i]['inCode'] = true;
+ }
+ }
+
+ $openTagStack[] = $name;
+ }
+ }
+ }
+
/**
* @inheritDoc
*/
$buffer .= $this->textArray[$i];
if ($tag['closing']) {
+ if (!empty($tag['invalid'])) {
+ // drop invalid closing tag
+ continue;
+ }
+ else if (!empty($tag['inCode'])) {
+ // revert bbcodes inside code
+ $buffer .= $tag['source'];
+ continue;
+ }
+
// get buffered opening tag
$openingTag = end($bufferedTagStack);
}
}
else {
+ if (!empty($tag['inCode'])) {
+ // revert bbcodes inside code
+ $buffer .= $tag['source'];
+ continue;
+ }
+
// opening tag
if ($this->needBuffering($tag)) {
// start buffering
if (isset($this->textArray[$i + 1])) $this->parsedText .= $this->textArray[$i + 1];
}
- /**
- * @inheritDoc
- */
- public function buildXMLStructure() {
- // stack for open tags
- $openTagStack = $openTagDataStack = [];
- $newTagArray = [];
- $newTextArray = [];
-
- $i = -1;
- foreach ($this->tagArray as $i => $tag) {
- if ($tag['closing']) {
- // closing tag
- if (in_array($tag['name'], $openTagStack) && $this->isAllowed($openTagStack, $tag['name'], true)) {
- // close unclosed tags
- while (($previousTag = end($openTagStack)) != $tag['name']) {
- $nextIndex = count($newTagArray);
-
- // mark as invalid and do not flag as opened tag
- $newTag = $this->buildTag('[/'.end($openTagStack).']');
- $newTag['invalid'] = true;
-
- $newTagArray[$nextIndex] = $newTag;
- if (!isset($newTextArray[$nextIndex])) $newTextArray[$nextIndex] = '';
- $newTextArray[$nextIndex] .= $this->textArray[$i];
- $this->textArray[$i] = '';
- array_pop($openTagStack);
- array_pop($openTagDataStack);
- }
-
- $nextIndex = count($newTagArray);
- $newTagArray[$nextIndex] = $tag;
- array_pop($openTagStack);
- array_pop($openTagDataStack);
- if (!isset($newTextArray[$nextIndex])) $newTextArray[$nextIndex] = '';
- $newTextArray[$nextIndex] .= $this->textArray[$i];
- }
- else {
- // no such tag open
- // handle as plain text
- $this->textArray[$i] .= $tag['source'];
- $last = count($newTagArray);
- if (!isset($newTextArray[$last])) $newTextArray[$last] = '';
- $newTextArray[$last] .= $this->textArray[$i];
- }
- }
- else {
- // opening tag
- if ($this->isAllowed($openTagStack, $tag['name']) && $this->isValidTag($tag)) {
- $openTagStack[] = $tag['name'];
- $openTagDataStack[] = $tag;
- $nextIndex = count($newTagArray);
- $newTagArray[$nextIndex] = $tag;
- if (!isset($newTextArray[$nextIndex])) $newTextArray[$nextIndex] = '';
- $newTextArray[$nextIndex] .= $this->textArray[$i];
- }
- else {
- // tag not allowed
- $this->textArray[$i] .= $tag['source'];
- $last = count($newTagArray);
- if (!isset($newTextArray[$last])) $newTextArray[$last] = '';
- $newTextArray[$last] .= $this->textArray[$i];
- }
- }
- }
-
- $last = count($newTagArray);
- if (!isset($newTextArray[$last])) $newTextArray[$last] = '';
- $newTextArray[$last] .= $this->textArray[$i + 1];
-
- // close unclosed open tags
- while (end($openTagStack)) {
- $nextIndex = count($newTagArray);
-
- // mark as invalid
- $newTag = $this->buildTag('[/'.end($openTagStack).']');
- $newTag['invalid'] = true;
-
- $newTagArray[$nextIndex] = $newTag;
- if (!isset($newTextArray[$nextIndex])) $newTextArray[$nextIndex] = '';
- array_pop($openTagStack);
- array_pop($openTagDataStack);
- }
-
- $this->tagArray = $newTagArray;
- $this->textArray = $newTextArray;
- }
-
/**
* Builds the bbcode output.
*
throw new SystemException("Tag mismatch, expected '".$name."', got '".$data['name']."'.");
}
- if (!empty($data['invalid'])) {
- // drop invalid closing tags
- return '';
- }
-
return '<woltlab-metacode-marker data-uuid="' . $data['uuid'] . '" data-source="' . base64_encode($tag['source']) . '" />';
}