88use DOMNode ;
99use DOMNodeList ;
1010use DOMText ;
11- use WMDE \VueJsTemplating \JsParsing \BasicJsExpressionParser ;
12- use WMDE \VueJsTemplating \JsParsing \CachingExpressionParser ;
13- use WMDE \VueJsTemplating \JsParsing \JsExpressionParser ;
1411
1512class Component {
1613
@@ -20,31 +17,28 @@ class Component {
2017 private $ rootNode ;
2118
2219 /**
23- * @var JsExpressionParser
20+ * @var DOMNode An arbitrary node to reparent cloned root nodes too,
21+ * so that they can still have a parent node.
22+ * (This is required for {@link self::isRemovedFromTheDom()}.)
2423 */
25- private $ expressionParser ;
24+ private $ cloneOwner ;
2625
27- /**
28- * @param DOMElement $rootNode
29- * @param callable[] $methods
30- */
31- public function __construct ( DOMElement $ rootNode , array $ methods ) {
26+ /** @var App */
27+ private $ app ;
28+
29+ public function __construct ( DOMElement $ rootNode , App $ app ) {
3230 $ this ->rootNode = $ rootNode ;
33- $ this ->expressionParser = new CachingExpressionParser ( new BasicJsExpressionParser ( $ methods ) );
34- }
31+ $ this ->app = $ app ;
3532
36- /**
37- * Note: this method is not currently safe to call repeatedly
38- * (the internal root node is modified in-place).
39- *
40- * @param array $data
41- *
42- * @return string HTML
43- */
44- public function render ( array $ data ) {
45- $ this ->handleNode ( $ this ->rootNode , $ data );
33+ $ this ->cloneOwner = $ rootNode ->ownerDocument ->documentElement ;
34+ }
4635
47- return $ this ->rootNode ->ownerDocument ->saveHTML ( $ this ->rootNode );
36+ public function render ( array $ data ): DOMElement {
37+ $ rootNode = $ this ->rootNode ->cloneNode ( true );
38+ $ this ->cloneOwner ->appendChild ( $ rootNode );
39+ $ this ->handleNode ( $ rootNode , $ data );
40+ $ this ->cloneOwner ->removeChild ( $ rootNode );
41+ return $ rootNode ;
4842 }
4943
5044 /**
@@ -60,11 +54,13 @@ private function handleNode( DOMNode $node, array $data ) {
6054 $ this ->handleRawHtml ( $ node , $ data );
6155
6256 if ( !$ this ->isRemovedFromTheDom ( $ node ) ) {
63- $ this ->handleAttributeBinding ( $ node , $ data );
64- $ this ->handleIf ( $ node ->childNodes , $ data );
57+ if ( !$ this ->handleComponent ( $ node , $ data ) ) {
58+ $ this ->handleAttributeBinding ( $ node , $ data );
59+ $ this ->handleIf ( $ node ->childNodes , $ data );
6560
66- foreach ( iterator_to_array ( $ node ->childNodes ) as $ childNode ) {
67- $ this ->handleNode ( $ childNode , $ data );
61+ foreach ( iterator_to_array ( $ node ->childNodes ) as $ childNode ) {
62+ $ this ->handleNode ( $ childNode , $ data );
63+ }
6864 }
6965 }
7066 }
@@ -99,8 +95,7 @@ private function replaceMustacheVariables( DOMNode $node, array $data ) {
9995 preg_match_all ( $ regex , $ text , $ matches );
10096
10197 foreach ( $ matches ['expression ' ] as $ index => $ expression ) {
102- $ value = $ this ->expressionParser ->parse ( $ expression )
103- ->evaluate ( $ data );
98+ $ value = $ this ->app ->evaluateExpression ( $ expression , $ data );
10499
105100 $ text = str_replace ( $ matches [0 ][$ index ], $ value , $ text );
106101 }
@@ -112,15 +107,38 @@ private function replaceMustacheVariables( DOMNode $node, array $data ) {
112107 }
113108 }
114109
110+ /** @return bool true if it was a component, false otherwise */
111+ private function handleComponent ( DOMElement $ node , array $ data ): bool {
112+ if ( strpos ( $ node ->tagName , '- ' ) === false ) {
113+ return false ;
114+ }
115+ $ componentName = $ node ->tagName ;
116+
117+ $ componentData = [];
118+ foreach ( $ node ->attributes as $ attribute ) {
119+ if ( str_starts_with ( $ attribute ->name , ': ' ) ) { // TODO also v-bind: ?
120+ $ name = substr ( $ attribute ->name , 1 );
121+ $ value = $ this ->app ->evaluateExpression ( $ attribute ->value , $ data );
122+ } else {
123+ $ name = $ attribute ->name ;
124+ $ value = $ attribute ->value ;
125+ }
126+ $ componentData [$ name ] = $ value ;
127+ }
128+ $ rendered = $ this ->app ->renderComponentToDOM ( $ componentName , $ componentData );
129+ // TODO use adoptNode() instead of importNode() in PHP 8.3+ (see php-src commit ed6df1f0ad)
130+ $ node ->replaceWith ( $ node ->ownerDocument ->importNode ( $ rendered , true ) );
131+ return true ;
132+ }
133+
115134 private function handleAttributeBinding ( DOMElement $ node , array $ data ) {
116135 /** @var DOMAttr $attribute */
117136 foreach ( iterator_to_array ( $ node ->attributes ) as $ attribute ) {
118137 if ( !str_starts_with ( $ attribute ->name , ': ' ) ) {
119138 continue ;
120139 }
121140
122- $ value = $ this ->expressionParser ->parse ( $ attribute ->value )
123- ->evaluate ( $ data );
141+ $ value = $ this ->app ->evaluateExpression ( $ attribute ->value , $ data );
124142
125143 $ name = substr ( $ attribute ->name , 1 );
126144 if ( is_bool ( $ value ) ) {
@@ -151,7 +169,7 @@ private function handleIf( DOMNodeList $nodes, array $data ) {
151169 if ( $ node ->hasAttribute ( 'v-if ' ) ) {
152170 $ conditionString = $ node ->getAttribute ( 'v-if ' );
153171 $ node ->removeAttribute ( 'v-if ' );
154- $ condition = $ this ->evaluateExpression ( $ conditionString , $ data );
172+ $ condition = $ this ->app -> evaluateExpression ( $ conditionString , $ data );
155173
156174 if ( !$ condition ) {
157175 $ nodesToRemove [] = $ node ;
@@ -219,16 +237,6 @@ private function handleRawHtml( DOMNode $node, array $data ) {
219237 }
220238 }
221239
222- /**
223- * @param string $expression
224- * @param array $data
225- *
226- * @return bool
227- */
228- private function evaluateExpression ( $ expression , array $ data ) {
229- return $ this ->expressionParser ->parse ( $ expression )->evaluate ( $ data );
230- }
231-
232240 private function removeNode ( DOMElement $ node ) {
233241 $ node ->parentNode ->removeChild ( $ node );
234242 }
0 commit comments