texy = $texy; $texy->addHandler('postProcess', array($this, 'postProcess')); } /** * Converts ... ... . * into ... ... */ public function postProcess($texy, & $s) { $this->space = $this->baseIndent; $this->tagStack = array(); $this->tagUsed = array(); $this->xml = $texy->getOutputMode() & Texy::XML; // special "base content" $this->baseDTD = $texy->dtd['div'][1] + $texy->dtd['html'][1] /*+ $texy->dtd['head'][1]*/ + $texy->dtd['body'][1] + array('html'=>1); // wellform and reformat $s = preg_replace_callback( '#(.*)<(?:(!--.*--)|(/?)([a-z][a-z0-9._:-]*)(|[ \n].*)\s*(/?))>()#Uis', array($this, 'cb'), $s . '' ); // empty out stack foreach ($this->tagStack as $item) $s .= $item['close']; // right trim $s = preg_replace("#[\t ]+(\n|\r|$)#", '$1', $s); // right trim // join double \r to single \n $s = str_replace("\r\r", "\n", $s); $s = strtr($s, "\r", "\n"); // greedy chars $s = preg_replace("#\\x07 *#", '', $s); // back-tabs $s = preg_replace("#\\t? *\\x08#", '', $s); // line wrap if ($this->lineWrap > 0) { $s = preg_replace_callback( '#^(\t*)(.*)$#m', array($this, 'wrap'), $s ); } // remove HTML 4.01 optional end tags if (!$this->xml && $this->removeOptional) { $s = preg_replace('#\\s*#u', '', $s); } } /** * Callback function: | | .... * @return string */ private function cb($matches) { // html tag list(, $mText, $mComment, $mEnd, $mTag, $mAttr, $mEmpty) = $matches; // [1] => text // [1] => !-- comment -- // [2] => / // [3] => TAG // [4] => ... (attributes) // [5] => / (empty) $s = ''; // phase #1 - stuff between tags if ($mText !== '') { $item = reset($this->tagStack); // text not allowed? if ($item && !isset($item['dtdContent']['%DATA'])) { } // inside pre & textarea preserve spaces elseif (!empty($this->tagUsed['pre']) || !empty($this->tagUsed['textarea']) || !empty($this->tagUsed['script'])) $s = Texy::freezeSpaces($mText); // otherwise shrink multiple spaces else $s = preg_replace('#[ \n]+#', ' ', $mText); } // phase #2 - HTML comment if ($mComment) return $s . '<' . Texy::freezeSpaces($mComment) . '>'; // phase #3 - HTML tag $mEmpty = $mEmpty || isset(TexyHtml::$emptyElements[$mTag]); if ($mEmpty && $mEnd) return $s; // bad tag; /end/ if ($mEnd) { // end tag // has start tag? if (empty($this->tagUsed[$mTag])) return $s; // autoclose tags $tmp = array(); $back = TRUE; foreach ($this->tagStack as $i => $item) { $tag = $item['tag']; $s .= $item['close']; $this->space -= $item['indent']; $this->tagUsed[$tag]--; $back = $back && isset(TexyHtml::$inlineElements[$tag]); unset($this->tagStack[$i]); if ($tag === $mTag) break; array_unshift($tmp, $item); } if (!$back || !$tmp) return $s; // allowed-check (nejspis neni ani potreba) $item = reset($this->tagStack); if ($item) $dtdContent = $item['dtdContent']; else $dtdContent = $this->baseDTD; if (!isset($dtdContent[$tmp[0]['tag']])) return $s; // autoopen tags foreach ($tmp as $item) { $s .= $item['open']; $this->space += $item['indent']; $this->tagUsed[$item['tag']]++; array_unshift($this->tagStack, $item); } } else { // start tag $dtdContent = $this->baseDTD; if (!isset($this->texy->dtd[$mTag])) { // unknown (non-html) tag $allowed = TRUE; $item = reset($this->tagStack); if ($item) $dtdContent = $item['dtdContent']; } else { // optional end tag closing foreach ($this->tagStack as $i => $item) { // is tag allowed here? $dtdContent = $item['dtdContent']; if (isset($dtdContent[$mTag])) break; $tag = $item['tag']; // auto-close hidden, optional and inline tags if ($item['close'] && (!isset(TexyHtml::$optionalEnds[$tag]) && !isset(TexyHtml::$inlineElements[$tag]))) break; // close it $s .= $item['close']; $this->space -= $item['indent']; $this->tagUsed[$tag]--; unset($this->tagStack[$i]); $dtdContent = $this->baseDTD; } // is tag allowed in this content? $allowed = isset($dtdContent[$mTag]); // check deep element prohibitions if ($allowed && isset(TexyHtml::$prohibits[$mTag])) { foreach (TexyHtml::$prohibits[$mTag] as $pTag) if (!empty($this->tagUsed[$pTag])) { $allowed = FALSE; break; } } } // empty elements se neukladaji do zasobniku if ($mEmpty) { if (!$allowed) return $s; if ($this->xml) $mAttr .= " /"; $indent = $this->indent && empty($this->tagUsed['pre']) && empty($this->tagUsed['textarea']); if ($indent && $mTag === 'br') // formatting exception return rtrim($s) . '<' . $mTag . $mAttr . ">\n" . str_repeat("\t", max(0, $this->space - 1)) . "\x07"; if ($indent && !isset(TexyHtml::$inlineElements[$mTag])) { $space = "\r" . str_repeat("\t", $this->space); return $s . $space . '<' . $mTag . $mAttr . '>' . $space; } return $s . '<' . $mTag . $mAttr . '>'; } $open = NULL; $close = NULL; $indent = 0; /* if (!isset(TexyHtml::$inlineElements[$mTag])) { // block tags always decorate with \n $s .= "\n"; $close = "\n"; } */ if ($allowed) { $open = '<' . $mTag . $mAttr . '>'; // receive new content (ins & del are special cases) if (!empty($this->texy->dtd[$mTag][1])) $dtdContent = $this->texy->dtd[$mTag][1]; // format output if ($this->indent && !isset(TexyHtml::$inlineElements[$mTag])) { $close = "\x08" . '' . "\n" . str_repeat("\t", $this->space); $s .= "\n" . str_repeat("\t", $this->space++) . $open . "\x07"; $indent = 1; } else { $close = ''; $s .= $open; } // TODO: problematic formatting of select / options, object / params } // open tag, put to stack, increase counter $item = array( 'tag' => $mTag, 'open' => $open, 'close' => $close, 'dtdContent' => $dtdContent, 'indent' => $indent, ); array_unshift($this->tagStack, $item); $tmp = &$this->tagUsed[$mTag]; $tmp++; } return $s; } /** * Callback function: wrap lines. * @return string */ private function wrap($m) { list(, $space, $s) = $m; return $space . wordwrap($s, $this->lineWrap, "\n" . $space); } }