Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
DOMFragmentBuilder
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 4
380
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 subpipelineUnnecessary
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
132
 buildDOMFragment
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
30
 onTag
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\Wt2Html\TT;
5
6use Wikimedia\Assert\Assert;
7use Wikimedia\Parsoid\Tokens\EndTagTk;
8use Wikimedia\Parsoid\Tokens\EOFTk;
9use Wikimedia\Parsoid\Tokens\SelfclosingTagTk;
10use Wikimedia\Parsoid\Tokens\TagTk;
11use Wikimedia\Parsoid\Tokens\Token;
12use Wikimedia\Parsoid\Utils\PipelineUtils;
13use Wikimedia\Parsoid\Utils\TokenUtils;
14use Wikimedia\Parsoid\Wt2Html\TokenTransformManager;
15
16class DOMFragmentBuilder extends TokenHandler {
17    /**
18     * @param TokenTransformManager $manager manager environment
19     * @param array $options options
20     */
21    public function __construct( TokenTransformManager $manager, array $options ) {
22        parent::__construct( $manager, $options );
23    }
24
25    /**
26     * Can/should content represented in 'toks' be processed in its own DOM scope?
27     * 1. No reason to spin up a new pipeline for plain text
28     * 2. In some cases, if templates need not be nested entirely within the
29     *    boundary of the token, we cannot process the contents in a new scope.
30     * @param array $toks
31     * @param Token $contextTok
32     * @return bool
33     */
34    private function subpipelineUnnecessary( array $toks, Token $contextTok ): bool {
35        for ( $i = 0,  $n = count( $toks );  $i < $n;  $i++ ) {
36            $t = $toks[$i];
37
38            // For wikilinks and extlinks, templates should be properly nested
39            // in the content section. So, we can process them in sub-pipelines.
40            // But, for other context-toks, we back out. FIXME: Can be smarter and
41            // detect proper template nesting, but, that can be a later enhancement
42            // when dom-scope-tokens are used in other contexts.
43            if ( $contextTok && $contextTok->getName() !== 'wikilink' &&
44                $contextTok->getName() !== 'extlink' &&
45                $t instanceof SelfclosingTagTk &&
46                 $t->getName() === 'meta' && TokenUtils::hasTypeOf( $t, 'mw:Transclusion' )
47            ) {
48                return true;
49            } elseif ( $t instanceof TagTk || $t instanceof EndTagTk || $t instanceof SelfclosingTagTk ) {
50                // Since we encountered a complex token, we'll process this
51                // in a subpipeline.
52                return false;
53            }
54        }
55
56        // No complex tokens at all -- no need to spin up a new pipeline
57        return true;
58    }
59
60    /**
61     * @param Token $scopeToken
62     * @return TokenHandlerResult
63     */
64    private function buildDOMFragment( Token $scopeToken ): TokenHandlerResult {
65        $contentKV = $scopeToken->getAttributeKV( 'content' );
66        $content = $contentKV->v;
67        if ( is_string( $content ) ||
68            $this->subpipelineUnnecessary( $content, $scopeToken->getAttribute( 'contextTok' ) )
69        ) {
70            // New pipeline not needed. Pass them through
71            return new TokenHandlerResult( is_string( $content ) ? [ $content ] : $content );
72        } else {
73            // Source offsets of content
74            $srcOffsets = $contentKV->srcOffsets;
75
76            // Without source offsets for the content, it isn't possible to
77            // compute DSR and template wrapping in content. So, users of
78            // mw:dom-fragment-token should always set offsets on content
79            // that comes from the top-level document.
80            Assert::invariant(
81                $this->options['inTemplate'] || (bool)$srcOffsets,
82                'Processing top-level content without source offsets'
83            );
84
85            $pipelineOpts = [
86                'inlineContext' => $scopeToken->getAttribute( 'inlineContext' ) === "1",
87                'expandTemplates' => $this->options['expandTemplates'],
88                'inTemplate' => $this->options['inTemplate']
89            ];
90
91            // Append EOF
92            $content[] = new EOFTk();
93
94            // Process tokens
95            $domFragment = PipelineUtils::processContentInPipeline(
96                $this->env,
97                $this->manager->getFrame(),
98                // Append EOF
99                $content,
100                [
101                    'pipelineType' => 'tokens/x-mediawiki/expanded',
102                    'pipelineOpts' => $pipelineOpts,
103                    'srcOffsets' => $srcOffsets->value,
104                    'sol' => true
105                ]
106            );
107
108            $toks = PipelineUtils::tunnelDOMThroughTokens(
109                $this->env,
110                $scopeToken,
111                $domFragment,
112                [ "pipelineOpts" => $pipelineOpts ]
113            );
114
115            return new TokenHandlerResult( $toks );
116        }
117    }
118
119    /**
120     * @inheritDoc
121     */
122    public function onTag( Token $token ): ?TokenHandlerResult {
123        return $token->getName() === 'mw:dom-fragment-token' ?
124            $this->buildDOMFragment( $token ) : null;
125    }
126}