* @license PHP License * @package wb * @subpackage Markup */ WBClass::load('WBString' , 'WBMarkup_Handler_Xml2Wxml'); /** * Markup Scanner Handler: XML2HTML * * Convert Wombat XML data to HTML * * @version 0.5.1 * @package wb * @subpackage Markup */ class WBMarkup_Handler_Xml2Html extends WBMarkup_Handler_Xml2Wxml { /** * actual wxml config * @var WBConfig */ private $config; /** * List of WBMarkup_Listener * @var array */ private $listener = array(); /** * template engine * @var patTemplate */ protected $tmpl; /** * template dir * @var string */ private $tmplDir = ''; /** * constructor * * Load config */ public function __construct() { $this->config = WBClass::create('WBConfig'); $this->config->load('wxml/config'); $this->initTmplEngine(); } /** * override default template dir * * @param string $dir */ public function setTmplDir($dir) { if (empty($dir)) { return; } $this->tmplDir = $dir; $this->initTmplEngine($dir); } /** * Initialize Template Engine * * Load default convert if available * @param string $dir */ private function initTmplEngine($dir = '') { if (empty($dir)) { $dir = '__default'; } $base = WBParam::get('wb/dir/base'); $tmpl = 'Wxml/converter/' . trim($dir, '/') . $this->getPatTemplateSuffix();; if (!$this->tmpl) { $this->tmpl = $this->createPatTemplate(); } $this->tmpl->readTemplatesFromInput($tmpl); } /** * Get patTemplate object * * @return patTemplate */ protected function createPatTemplate() { return WBClass::create('patTemplate'); } /** * Get File Name Suffix * * @return string */ protected function getPatTemplateSuffix() { return '.tmpl'; } /** * handler on start beginning of scan * * * * @param string $content * @return bool usually true, false to stop the scanner */ public function onScanStart(&$content) { if (!parent::onScanStart($content)) { return false; } foreach ($this->listener as $l) { $l->onScanStart($content); } return true; } /** * add tag listener * * @param WBMarkup_Listener $l */ public function addListener($l) { $l->onScanStart($this->content); $this->listener[] = $l; } /** * handler on start element * * @param string $ns The used namespace, if set. otherwise null * @param string $tag the tag itself * @param array $attributes the attributes of the found element * @param bool $isEmpty true if it is a start- and closing-Element (e.g.
) * @return bool usually true, false to stop the scanner */ public function onStartElement($ns, $tag, $attributes, $isEmpty) { if (!parent::onStartElement($ns, $tag, $attributes, $isEmpty)) { return false; } foreach ($this->listener as $l) { $l->onStartElement($this->doc[$this->depth]); } return true; } /** * handler fo cData * * Simply add cDate to document * * Copied regular expression to find and replace URL from * {@link http://snipplr.com/view/6889/regular-expressions-for-uri-validationparsing/} * and removed parentheses * * @uses replaceUrlString() * @param string $cData character data * @return bool usually true, false to stop the scanner */ public function onCharacterData($cData) { // find and replace URL strings if (strstr($cData, '://')) { $regex = '/(https?|ftp):\\/\\/((?:[a-z0-9@:.-]|%[0-9A-F]{2}){3,})(?::(\\d+))?((?:\\/(?:[a-z0-9-._~!$&\'*+,;=:@]|%[0-9A-F]{2})*)*)(?:\\?((?:[a-z0-9-._~!$&\'*+,;=:\\/?@]|%[0-9A-F]{2})*))?(?:#((?:[a-z0-9-._~!$&\'*+,;=:\\/?@]|%[0-9A-F]{2})*))?/i'; $cData = preg_replace_callback($regex , array($this, 'replaceUrlString') , $cData); } return parent::onCharacterData($cData); } /** * test handler on end element * * @param string $ns The used namespace, if set. otherwise null * @param string $tag the tag itself * @param bool $empty defines if the tag is empty * @return bool usually true, false to stop the scanner */ public function onEndElement($ns, $tag, $empty) { // serialize node if( !$this->depth ) { return true; } $node = array_pop($this->doc); // inform listener foreach ($this->listener as $l) { $l->onEndElement($node); } if ($node['ns']) { $this->convert($node['ns'], $node['tag'], $node); } else { $this->convertDefault($node['tag'], $node); } --$this->depth; if( $this->depth < 0 ) { $this->depth = 0; } $this->doc[$this->depth]['cData'][] = $this->node2String($node); return true; } /** * called right after scan is complete * * @return bool usually true, false to stop the scanner */ public function onScanComplete() { $this->content = $this->node2String($this->doc[0]); // inform listener foreach ($this->listener as $l) { $l->onScanComplete($this->content); } // inform converter foreach (array_keys($this->con) as $con) { $this->con[$con]->reset(); } // flush listener $this->listener = array(); return true; } /** * convert node * * transform namespaced tags to HTML * * @todo convert empty namespace (standard HTML) to known WB namespace, if possible * @param string $ns * @param string $tag * @param array $node */ protected function convert($ns, $tag, &$node) { $con = $this->getConverter($ns, $tag); if (!$con) { // remove tag $html = array( 'ns' => null, 'tag' => '', 'attributes' => array(), 'isEmpty' => false, 'cData' => $node['cData'] ); $node = $html; return; } if (!empty($this->tmplDir)) { $con->setTmplDir($this->tmplDir); } return $con->toHtml($node); } /** * convert node of default namespace * * @param string $tag * @param array $node */ protected function convertDefault($tag, &$node) { if (!$this->tmpl || !$this->tmpl->exists('snippet_' . $tag)) { $this->handleBr($node); $this->insertAutoLink($node); return; } $html = array( 'ns' => null, 'tag' => '', 'attributes' => array(), 'isEmpty' => false, 'cData' => array() ); $cData = implode('', $node['cData']); $this->tmpl->clearTemplate('snippet_' . $tag); $this->tmpl->addVars('snippet_' . $tag, $node['attributes'], 'ATT_'); $this->tmpl->addVar('snippet_' . $tag, 'CDATA', $cData); $html['cData'][] = $this->tmpl->getParsedTemplate('snippet_' . $tag); $node = $html; } /** * Handle BR * * Either strip or keep BR-tags * * @param array $node */ private function handleBr(&$node) { if (!empty($node['ns']) || 'br' != $node['tag']) { return; } $action = $this->config->get('html/br/convert', 'keep'); switch (strtolower($action)) { case 'strip': $node = array( 'ns' => null, 'tag' => '', 'attributes' => array(), 'isEmpty' => false, 'cData' => array() ); break; default: case 'keep': break; } } /** * add auto links for each headline * * See in config whether to add a link to this tag. * * @param array $node */ private function insertAutoLink(&$node) { // only headline tags h1-h6 if (!empty($node['ns']) || !in_array($node['tag'], array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'))) { return; } $tags = $this->config->get('html/heading/autolink/tag', array()); $title = $this->config->get('html/heading/autolink/title', 'Link to paragraph %s'); if (empty($tags)) { return; } if (!in_array($node['tag'], $tags)) { return; } $cData = $node['cData']; $cData = implode("\n", $cData); $cData = strip_tags($cData); $cData = str_replace(' ', ' ', $cData); $cData = trim($cData); $title = sprintf($title, $cData); $cData = substr($cData, 0, 40); $cData = urlencode($cData); array_unshift($node['cData'], ''); $class = $this->config->get('html/heading/autolink/class', 'wxml paragraph'); $cd = $this->config->get('html/heading/autolink/cdata', '¶'); array_push($node['cData'], sprintf('%s', $class, $title, $cData, $cd)); } /** * replace URL string with propper node * * @see preg_replace_callback() * @param array $match * @return string */ public function replaceUrlString($match) { // convert URL to node $atts = array( 'type' => 'external', 'href' => $match[1] . '://' . $match[2] . $match[4], 'title' => '' ); // parameter? if (isset($match[5])) { $atts['href'] .= '?' . $match[5]; } $atts['title'] = urldecode($atts['href']); $this->onStartElement('wb', 'a', $atts, false); $this->doc[$this->depth]['cData'][] = $atts['title']; $this->onEndElement('wb', 'a', false); return array_pop($this->doc[$this->depth]['cData']); } }