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:----gOjjv5/QK9t01dfrlTRDhCfn2jqLiBvAU6IWhry8VGkxZOiQkoyjpMI67kfupTdPHujQ9t83wlb2uBu/MTxRA7CbY0DwLWH2Ua5ElOyYn2b/DHC1RIMN+9U10GL3XkxPWMd3zJWVkLkAVyJPD8SwwOASQM6eRER+v7YMzidWjsFsFV/7JZBDSBdhLj7qPimPb5CUEKVXyNjgnGf94FN1Ecg5l5gx4zyf2UOD/qEqYYDigNR2L8fSa3dytMSO89LTxAAghdr03A+DN68C9pYejgcy3kiqHKa7OCpx3BKuG7xj8vGKaIq/CTGH9vYBVvXY8kc8L4pOrrJB6X2T87mXLksUaFSvaUNMG8L5GNR03H0qgHCcMg9ASNBG5t0Ywkjg+Xt1ocWVVtoDq640yyomcmCDucoQizxHivm92ybl9e+PHNz9LxoELo3QpR78oMoHFowV17iKacRoMyjXAOSsjjAYUQp/EQ8r+CbzVyiD8hqWJFMyxTmRnEAxGTNPpmHwqPPJwEkDoJwWGxAyYpKUt/TJQpR5slOhZiiLWvPeWX/Y5QwCeKd4Qgp+3ok5n61etPhR7JE13fROySk2sjs1f/8tJZk8DythQLl3tPssYBNSAqsnRgnN1j5DpV5YZpKWkQ/K/9cRGevNrNjCId/SdbvN/18eeCrfpMVRccrzncI=----ATTACHMENT:----NTAyMDc2NDI2ODAzNDA5IDQ1MTY0MTIyMzg1MzAyMDQgODE3MDc3NDYyMTMyMDc3Mw==