Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 119
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
ParserTestPFragmentHandlers
0.00% covered (danger)
0.00%
0 / 119
0.00% covered (danger)
0.00%
0 / 3
462
0.00% covered (danger)
0.00%
0 / 1
 getParserTestConfigFileName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPFragmentHandlersConfig
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 1
2
 getHandler
0.00% covered (danger)
0.00%
0 / 81
0.00% covered (danger)
0.00%
0 / 1
380
1<?php
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\ParserTests;
5
6use Wikimedia\Assert\UnreachableException;
7use Wikimedia\Parsoid\Ext\Arguments;
8use Wikimedia\Parsoid\Ext\AsyncResult;
9use Wikimedia\Parsoid\Ext\ParsoidExtensionAPI;
10use Wikimedia\Parsoid\Ext\PFragmentHandler;
11use Wikimedia\Parsoid\Fragments\HtmlPFragment;
12use Wikimedia\Parsoid\Fragments\LiteralStringPFragment;
13use Wikimedia\Parsoid\Fragments\PFragment;
14use Wikimedia\Parsoid\Fragments\WikitextPFragment;
15
16/**
17 * Various PFragmentHandler implementations used in parser tests.
18 * @see ParserHook for registration
19 */
20class ParserTestPFragmentHandlers {
21
22    /**
23     * Ensure that both integrated and standalone test runners have the
24     * magic word definitions used by these PFragment handlers.
25     * @see SiteConfig::getCustomSiteConfigFileName()
26     * @see ParserTestRunner::staticSetup() (in core)
27     */
28    public static function getParserTestConfigFileName(): string {
29        return __DIR__ . "/ParserTests.siteconfig.json";
30    }
31
32    /**
33     * Return a configuration fragment for the PFragmentHandlers defined
34     * here.
35     * @see ParserHook::getConfig()
36     */
37    public static function getPFragmentHandlersConfig(): array {
38        // This is a list of "normal" parser function PFragment handlers;
39        // no special options.
40        $normalPFs = [
41            // Following keys must be present in ParserTests.siteconfig.json
42            // and as cases in ::getHandler() below.
43            'f1_wt', 'f2_if', 'f3_uc',
44            'f4_return_html', 'f5_from_nowiki',
45            'f7_kv', 'f8_countargs',
46        ];
47        $handlerFactory = self::class . '::getHandler';
48        $pFragmentConfig = array_map( static fn ( $key ) => [
49            'key' => $key,
50            'handler' => [
51                'factory' => $handlerFactory,
52                'args' => [ $key ],
53            ],
54            'options' => [ 'parserFunction' => true, ],
55        ], $normalPFs );
56
57        // "Uncommon" parser function PFragment handlers
58        $pFragmentConfig[] = [
59            'key' => 'f1_wt_nohash',
60            'handler' => [
61                'factory' => $handlerFactory,
62                'args' => [ "f1_wt_nohash" ],
63            ],
64            'options' => [
65                'parserFunction' => true,
66                # 'magicVariable' => true, # Not yet implemented: T391063
67                'nohash' => true,
68            ],
69        ];
70        $pFragmentConfig[] = [
71            'key' => 'f6_async_return',
72            'handler' => [
73                'factory' => $handlerFactory,
74                'args' => [ "f6_async_return" ]
75            ],
76            'options' => [
77                'parserFunction' => true,
78                'hasAsyncContent' => true,
79            ],
80        ];
81        return $pFragmentConfig;
82    }
83
84    /**
85     * Return a handler for a registered parser function
86     *
87     * @param string $fn
88     *
89     * @return PFragmentHandler
90     */
91    public static function getHandler( string $fn ): PFragmentHandler {
92        switch ( $fn ) {
93            case 'f1_wt':
94            case 'f1_wt_nohash':
95                // This is a test function which simply concatenates all of
96                // its (ordered) arguments.
97                return new class extends PFragmentHandler {
98                    /** @inheritDoc */
99                    public function sourceToFragment(
100                        ParsoidExtensionAPI $extApi,
101                        Arguments $arguments,
102                        bool $tagSyntax
103                    ) {
104                        $result = [];
105                        foreach ( $arguments->getOrderedArgs( $extApi ) as $a ) {
106                            $result[] = $a;
107                            $result[] = ' ';
108                        }
109                        if ( $result ) {
110                            array_pop( $result ); // remove trailing space
111                        }
112                        return WikitextPFragment::newFromSplitWt(
113                            $result, null, true
114                        );
115                    }
116                };
117
118            case 'f2_if':
119                // This is our implementation of the {{#if:..}} parser function.
120                // Extension or other fragments will evaluate to 'true'
121                return new class extends PFragmentHandler {
122                    /** @inheritDoc */
123                    public function sourceToFragment(
124                        ParsoidExtensionAPI $extApi,
125                        Arguments $arguments,
126                        bool $tagSyntax
127                    ) {
128                        $args = $arguments->getOrderedArgs( $extApi, false );
129                        $test = $args[0] ?? null;
130                        if ( $test === null ) {
131                            $result = '';
132                        } else {
133                            // Eager evaluation of the 'test'
134                            $test = $test->expand( $extApi );
135                            if ( $test->containsMarker() ) {
136                                $result = '1'; // non-empty value
137                            } else {
138                                $result = trim( $test->killMarkers() );
139                            }
140                        }
141                        $empty = WikitextPFragment::newFromLiteral( '', null );
142                        // Note that we are doing lazy evaluation of the
143                        // 'then' and 'else' branches, mostly as a test case
144                        // and demonstration.  The actual {{#if}}
145                        // implementation in core eagerly evaluates the result
146                        // in order to trim() it.
147                        if ( $result !== '' ) {
148                            return $args[1] ?? $empty;
149                        } else {
150                            return $args[2] ?? $empty;
151                        }
152                    }
153                };
154
155            case 'f3_uc':
156                // This is our implementation of the {{uc:..}} parser function.
157                // It skips over extension and other DOM fragments (legacy
158                // parser uses markerSkipCallback in core).
159                return new class extends PFragmentHandler {
160                    /** @inheritDoc */
161                    public function sourceToFragment(
162                        ParsoidExtensionAPI $extApi,
163                        Arguments $arguments,
164                        bool $tagSyntax
165                    ) {
166                        // Expand before using markerSkipCallback, or else
167                        // we'll end up expanding inside extension tags, etc.
168                        $s = $arguments->getOrderedArgs( $extApi )[0] ??
169                           WikitextPFragment::newFromLiteral( '', null );
170                        return $s->markerSkipCallback( "mb_strtoupper" );
171                    }
172                };
173
174            case 'f4_return_html':
175                // Demonstrate returning an HTML fragment from a parser
176                // function
177                return new class extends PFragmentHandler {
178                    /** @inheritDoc */
179                    public function sourceToFragment(
180                        ParsoidExtensionAPI $extApi,
181                        Arguments $arguments,
182                        bool $tagSyntax
183                    ) {
184                        return HtmlPFragment::newFromHtmlString(
185                            'html <b> contents',
186                            null
187                        );
188                    }
189                };
190
191            case 'f5_from_nowiki':
192                // Demonstrate fetching the raw text of an argument which
193                // was protected with <nowiki>
194                return new class extends PFragmentHandler {
195                    /** @inheritDoc */
196                    public function sourceToFragment(
197                        ParsoidExtensionAPI $extApi,
198                        Arguments $arguments,
199                        bool $tagSyntax
200                    ) {
201                        $s = $arguments->getOrderedArgs( $extApi )[0] ?? null;
202                        $s = ( $s === null ) ? '' : $s->toRawText( $extApi );
203                        // reverse the string to demonstrate processing;
204                        // this also disrupts/reveals any &-entities!
205                        $s = strrev( $s );
206                        // LiteralStringPFragment can be chained between
207                        // FragmentHandlers to safely pass raw text.
208                        return LiteralStringPFragment::newFromLiteral( $s, null );
209                    }
210                };
211
212            case 'f6_async_return':
213                // Demonstrate a conditionally-asynchronous return.
214                return new class extends PFragmentHandler {
215                    /** @inheritDoc */
216                    public function sourceToFragment(
217                        ParsoidExtensionAPI $extApi,
218                        Arguments $arguments,
219                        bool $tagSyntax
220                    ) {
221                        $args = $arguments->getOrderedArgs( $extApi, [
222                            true, false // 'content' argument is lazy
223                        ] );
224                        $notready = $args[0] ? $args[0]->toRawText( $extApi ) : '';
225                        $content = $args[1] ?? null; // lazy
226                        if ( $notready == 'not ready' ) {
227                            // Return "not ready yet" with $content
228                            // (which could be null if second arg is missing)
229                            return new class( $content ) extends AsyncResult {
230                                public ?PFragment $content;
231
232                                public function __construct( ?PFragment $f ) {
233                                    $this->content = $f;
234                                }
235
236                                public function fallbackContent( ParsoidExtensionAPI $extAPI ): ?PFragment {
237                                    return $this->content;
238                                }
239                            };
240                        }
241                        // The content is "ready", just return $content.
242                        return $content ??
243                            LiteralStringPFragment::newFromLiteral( '<missing>', null );
244                    }
245                };
246
247            case 'f7_kv':
248                // Demonstrate Arguments as return value
249                return new class extends PFragmentHandler {
250                    /** @inheritDoc */
251                    public function sourceToFragment(
252                        ParsoidExtensionAPI $extApi,
253                        Arguments $arguments,
254                        bool $tagSyntax
255                    ) {
256                        return new class( $arguments ) extends PFragment implements Arguments {
257                            private Arguments $arguments;
258
259                            public function __construct( Arguments $arguments ) {
260                                parent::__construct( null );
261                                $this->arguments = $arguments;
262                            }
263
264                            /** @inheritDoc */
265                            public function asHtmlString( ParsoidExtensionAPI $extApi ): string {
266                                return '(arguments)';
267                            }
268
269                            /** @inheritDoc */
270                            public function getOrderedArgs(
271                                ParsoidExtensionAPI $extApi,
272                                $expandAndTrim = true
273                            ): array {
274                                return $this->arguments->getOrderedArgs( $extApi, $expandAndTrim );
275                            }
276
277                            /** @inheritDoc */
278                            public function getNamedArgs(
279                                ParsoidExtensionAPI $extApi,
280                                $expandAndTrim = true
281                            ): array {
282                                return $this->arguments->getNamedArgs( $extApi, $expandAndTrim );
283                            }
284                        };
285                    }
286                };
287
288            case 'f8_countargs':
289                // This is a test function which simply reports the number
290                // of ordered arguments.
291                return new class extends PFragmentHandler {
292                    /** @inheritDoc */
293                    public function sourceToFragment(
294                        ParsoidExtensionAPI $extApi,
295                        Arguments $arguments,
296                        bool $tagSyntax
297                    ) {
298                        return LiteralStringPFragment::newFromLiteral(
299                            strval( count( $arguments->getOrderedArgs( $extApi ) ) ),
300                            null
301                        );
302                    }
303                };
304
305            default:
306                throw new UnreachableException( "Unknown parser function $fn" );
307        }
308    }
309}