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    private function buildDOMFragment( Token $scopeToken ): TokenHandlerResult {
61        $contentKV = $scopeToken->getAttributeKV( 'content' );
62        $content = $contentKV->v;
63        if ( is_string( $content ) ||
64            $this->subpipelineUnnecessary( $content, $scopeToken->getAttributeV( 'contextTok' ) )
65        ) {
66            // New pipeline not needed. Pass them through
67            return new TokenHandlerResult( is_string( $content ) ? [ $content ] : $content );
68        } else {
69            // Source offsets of content
70            $srcOffsets = $contentKV->srcOffsets;
71
72            // Without source offsets for the content, it isn't possible to
73            // compute DSR and template wrapping in content. So, users of
74            // mw:dom-fragment-token should always set offsets on content
75            // that comes from the top-level document.
76            Assert::invariant(
77                $this->options['inTemplate'] || (bool)$srcOffsets,
78                'Processing top-level content without source offsets'
79            );
80
81            $pipelineOpts = [
82                'inlineContext' => $scopeToken->getAttributeV( 'inlineContext' ) === "1",
83                'expandTemplates' => $this->options['expandTemplates'],
84                'inTemplate' => $this->options['inTemplate']
85            ];
86
87            // Append EOF
88            $content[] = new EOFTk();
89
90            // Process tokens
91            $domFragment = PipelineUtils::processContentInPipeline(
92                $this->env,
93                $this->manager->getFrame(),
94                // Append EOF
95                $content,
96                [
97                    'pipelineType' => 'expanded-tokens-to-fragment',
98                    'pipelineOpts' => $pipelineOpts,
99                    'srcOffsets' => $srcOffsets->value,
100                    'sol' => true
101                ]
102            );
103
104            $toks = PipelineUtils::tunnelDOMThroughTokens(
105                $this->env,
106                $scopeToken,
107                $domFragment,
108                [ "pipelineOpts" => $pipelineOpts ]
109            );
110
111            return new TokenHandlerResult( $toks );
112        }
113    }
114
115    /**
116     * @inheritDoc
117     */
118    public function onTag( Token $token ): ?TokenHandlerResult {
119        return $token->getName() === 'mw:dom-fragment-token' ?
120            $this->buildDOMFragment( $token ) : null;
121    }
122}