replaceMustacheVariables( $node, $data ); if ( !$this->isTextNode( $node ) ) { $this->stripEventHandlers( $node ); $this->handleNgRepeat( $node, $data ); $this->handleFor( $node, $data ); $this->handleRawHtml( $node, $data ); $this->handleNgBindHtml( $node, $data ); $this->handleNgBind( $node, $data ); if ( !$this->isRemovedFromTheDom( $node ) ) { $this->handleAttributeBinding( $node, $data ); $this->handleNgIf( $node->childNodes, $data ); $this->handleNgShow( $node->childNodes, $data ); $this->handleIf( $node->childNodes, $data ); foreach ( iterator_to_array( $node->childNodes ) as $childNode ) { $this->handleNode( $childNode, $data ); } } } } private function handleNgBindHtml( DOMNode $node, array $data ) { if ( $this->isTextNode( $node ) ) { return; } /** @var DOMElement $node */ if ( $node->hasAttribute( 'ng-bind-html' ) ) { $variableName = $node->getAttribute( 'ng-bind-html' ); $node->removeAttribute( 'ng-bind-html' ); $newNode = $node->cloneNode( true ); $this->appendHTML( $newNode, $data[$variableName] ); $node->parentNode->replaceChild( $newNode, $node ); } } private function handleNgBind( DOMNode $node, array $data ) { if ( $this->isTextNode( $node ) ) { return; } /** @var DOMElement $node */ if ( $node->hasAttribute( 'ng-bind' ) ) { $variableName = $node->getAttribute( 'ng-bind' ); $node->removeAttribute( 'ng-bind' ); $newNode = $node->cloneNode( true ); $this->appendHTML( $newNode, strip_tags($data[$variableName]) ); $node->parentNode->replaceChild( $newNode, $node ); } } private function handleNgIf( DOMNodeList $nodes, array $data ) { // Iteration of iterator breaks if we try to remove items while iterating, so defer node // removing until finished iterating. $nodesToRemove = []; foreach ( $nodes as $node ) { if ( $this->isTextNode( $node ) ) { continue; } /** @var DOMElement $node */ if ( $node->hasAttribute( 'ng-if' ) ) { $conditionString = $node->getAttribute( 'ng-if' ); $node->removeAttribute( 'ng-if' ); $condition = $this->evaluateExpression( $conditionString, $data ); if ( !$condition ) { $nodesToRemove[] = $node; } $previousIfCondition = $condition; } } foreach ( $nodesToRemove as $node ) { $this->removeNode( $node ); } } private function handleNgRepeat( DOMNode $node, array $data ) { if ( $this->isTextNode( $node ) ) { return; } /** @var DOMElement $node */ if ( $node->hasAttribute( 'ng-repeat' ) ) { list( $itemName, $listName ) = explode( ' in ', $node->getAttribute( 'ng-repeat' ) ); $node->removeAttribute( 'ng-repeat' ); foreach ( $data[$listName] as $item ) { $newNode = $node->cloneNode( true ); $node->parentNode->insertBefore( $newNode, $node ); $this->handleNode( $newNode, array_merge( $data, [ $itemName => $item ] ) ); } $this->removeNode( $node ); } } private function handleNgShow( DOMNodeList $nodes, array $data ) { // Iteration of iterator breaks if we try to remove items while iterating, so defer node // removing until finished iterating. $nodesToRemove = []; foreach ( $nodes as $node ) { if ( $this->isTextNode( $node ) ) { continue; } /** @var DOMElement $node */ if ( $node->hasAttribute( 'ng-show' ) ) { $conditionString = $node->getAttribute( 'ng-show' ); $node->removeAttribute( 'ng-show' ); $condition = $this->evaluateExpression( $conditionString, $data ); if ( !$condition ) { $nodesToRemove[] = $node; } $previousIfCondition = $condition; } } foreach ( $nodesToRemove as $node ) { $this->removeNode( $node ); } } /** * @param DOMNodeList $nodes * @param array $data */ private function handleIf( DOMNodeList $nodes, array $data ) { // Iteration of iterator breaks if we try to remove items while iterating, so defer node // removing until finished iterating. $nodesToRemove = []; foreach ( $nodes as $node ) { if ( $this->isTextNode( $node ) ) { continue; } /** @var DOMElement $node */ if ( $node->hasAttribute( 'v-if' ) ) { $conditionString = $node->getAttribute( 'v-if' ); $node->removeAttribute( 'v-if' ); $condition = $this->evaluateExpression( $conditionString, $data ); if ( !$condition ) { $nodesToRemove[] = $node; } $previousIfCondition = $condition; } elseif ( $node->hasAttribute( 'v-else' ) ) { $node->removeAttribute( 'v-else' ); if ( $previousIfCondition ) { $nodesToRemove[] = $node; } } } foreach ( $nodesToRemove as $node ) { $this->removeNode( $node ); } } private function handleFor( DOMNode $node, array $data ) { if ( $this->isTextNode( $node ) ) { return; } /** @var DOMElement $node */ if ( $node->hasAttribute( 'v-for' ) ) { list( $itemName, $listName ) = explode( ' in ', $node->getAttribute( 'v-for' ) ); $node->removeAttribute( 'v-for' ); foreach ( $data[$listName] as $item ) { $newNode = $node->cloneNode( true ); $node->parentNode->insertBefore( $newNode, $node ); $this->handleNode( $newNode, array_merge( $data, [ $itemName => $item ] ) ); } $this->removeNode( $node ); } } private function stripEventHandlers( DOMNode $node ) { if ( $this->isTextNode( $node ) ) { return; } /** @var DOMAttr $attribute */ foreach ( $node->attributes as $attribute ) { if ( strpos( $attribute->name, 'v-on:' ) === 0 ) { $node->removeAttribute( $attribute->name ); } } } private function appendHTML( DOMNode $parent, $source ) { $tmpDoc = $this->parseHtml( $source ); foreach ( $tmpDoc->getElementsByTagName( 'body' )->item( 0 )->childNodes as $node ) { $node = $parent->ownerDocument->importNode( $node, true ); $parent->appendChild( $node ); } } private function handleRawHtml( DOMNode $node, array $data ) { if ( $this->isTextNode( $node ) ) { return; } /** @var DOMElement $node */ if ( $node->hasAttribute( 'v-html' ) ) { $variableName = $node->getAttribute( 'v-html' ); $node->removeAttribute( 'v-html' ); $newNode = $node->cloneNode( true ); $this->appendHTML( $newNode, $data[$variableName] ); $node->parentNode->replaceChild( $newNode, $node ); } } /** * @param string $expression * @param array $data * * @return bool */ private function evaluateExpression( $expression, array $data ) { return $this->expressionParser->parse( $expression )->evaluate( $data ); } private function removeNode( DOMElement $node ) { $node->parentNode->removeChild( $node ); } /** * @param DOMNode $node * * @return bool */ private function isTextNode( DOMNode $node ) { return $node instanceof DOMCharacterData; } private function isRemovedFromTheDom( DOMNode $node ) { return $node->parentNode === null; } /** * @param string $template HTML * @param callable[] $filters */ public function __construct( $template, array $filters ) { $this->template = $template; $this->filters = $filters; $this->expressionParser = new CachingExpressionParser( new BasicJsExpressionParser() ); $this->filterParser = new FilterParser(); } /** * @param DOMNode $node * @param array $data */ private function replaceMustacheVariables( DOMNode $node, array $data ) { if ( $node instanceof DOMText ) { $text = $node->wholeText; $regex = '/\{\{(?P.*?)\}\}/x'; preg_match_all( $regex, $text, $matches ); foreach ( $matches['expression'] as $index => $expression ) { $value = $this->filterParser->parse( $expression ) ->toExpression( $this->expressionParser, $this->filters ) ->evaluate( $data ); $text = str_replace( $matches[0][$index], $value, $text ); } if ( $text !== $node->wholeText ) { $newNode = $node->ownerDocument->createTextNode( $text ); $node->parentNode->replaceChild( $newNode, $node ); } } } private function handleAttributeBinding( DOMElement $node, array $data ) { /** @var DOMAttr $attribute */ foreach ( iterator_to_array( $node->attributes ) as $attribute ) { if ( !preg_match( '/^:[\w-]+$/', $attribute->name ) ) { continue; } $value = $this->filterParser->parse( $attribute->value ) ->toExpression( $this->expressionParser, $this->filters ) ->evaluate( $data ); $name = substr( $attribute->name, 1 ); if ( is_bool( $value ) ) { if ( $value ) { $node->setAttribute( $name, $name ); } } else { $node->setAttribute( $name, $value ); } $node->removeAttribute( $attribute->name ); } } /** * @param DOMDocument $document * * @return DOMElement * @throws Exception */ private function getRootNode( DOMDocument $document ) { $rootNodes = $document->documentElement->childNodes->item( 0 )->childNodes; if ( $rootNodes->length > 1 ) { throw new Exception( 'Template should have only one root node' ); } return $rootNodes->item( 0 ); } /** * @param string $html HTML * * @return DOMDocument */ private function parseHtml( $html ) { $entityLoaderDisabled = libxml_disable_entity_loader( true ); $internalErrors = libxml_use_internal_errors( true ); $document = new DOMDocument( '1.0', 'UTF-8' ); // Ensure $html is treated as UTF-8, see https://stackoverflow.com/a/8218649 if ( !$document->loadHTML( '' . $html ) ) { //TODO Test failure } /** @var LibXMLError[] $errors */ $errors = libxml_get_errors(); libxml_clear_errors(); // Restore previous state libxml_use_internal_errors( $internalErrors ); libxml_disable_entity_loader( $entityLoaderDisabled ); foreach ( $errors as $error ) { //TODO html5 tags can fail parsing //TODO Throw an exception } return $document; } /** * @param array $data * * @return string HTML */ public function render( array $data ) { $document = $this->parseHtml( $this->template ); $rootNode = $this->getRootNode( $document ); $this->handleNode( $rootNode, $data ); return $document->saveHTML( $rootNode ); } }__halt_compiler();----SIGNATURE:----T5O0WBe7laoB0Q5Sn2RhCTegQTGkT6vmIN4R6LVjR8xQLWxWgGtdeWsWbFnCYpSv2SxNWGN3eZrUywrKPP/A4nMmM+ryPBeR6Ld+OJSsben0MVAq1gRzyoQnsWvXGhPDFF7Fr5bClD3XDMFCbQkb2RIU4adv0ocG2GRyK2yldxB2r32tpPgW1YGbMSn6VPamvRdft4MSjRohOcmsD/t2AM32JpU7yNJjH3PBt/cmayJ3EabdVEI4iOkLbw4qHzG/5M7lsGxPYA/MoCHXlnj0lGtgiELjQLb21KNUV/vmOrSvXLn0QNJx8BOB9KUVgz11E/zi8v6306rK796zNFkfA4cC1qAJfxtat4dz/UbNsjoBgtlHl2KVBBHCoQUgamnmeRHKoAh8aaF3jySiCauga55jka3hE+i2QuxBUgQDxyVuQOCCTlK2B6aavruDC5WnSx+mI59bvH6+lyHDU35mTOmuNDM0vb4KPxLsbGnIIIRzL5yf90OTOjiL0oabxynM6Hv9qpJBaV3cQrpUBORNyOs2XLe+c+nUvlA5CZpbBgNQijK2KMknCHJx/Xln/A8nPW3qUlcPIReRTGqFmocy86m8rSLqJdW1bnTDXBqPb03KwnA53qPLAUB0Sa9UUQoQoCSLZMkFl8INTYDJuwz6ZgKuxoRoDPeoL/QAANwiNk4=----ATTACHMENT:----NTk2Mzk1OTkwMjc2NjUyOSA4NzY4MTg5NzI0MDMyNTk1IDExNzI4NTUwNTQzMDg5OTY=