Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
RemexPipeline
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 6
72
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
12
 insertUnfosteredMeta
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 insertImplicitStartTag
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 insertExplicitStartTag
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 insertExplicitEndTag
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 isFosterablePosition
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\Wt2Html\TreeBuilder;
5
6use Wikimedia\Parsoid\Config\Env;
7use Wikimedia\Parsoid\Core\InternalException;
8use Wikimedia\Parsoid\DOM\Document;
9use Wikimedia\Parsoid\DOM\Element;
10use Wikimedia\Parsoid\Wikitext\Consts;
11use Wikimedia\RemexHtml\Tokenizer\PlainAttributes;
12use Wikimedia\RemexHtml\Tokenizer\Tokenizer;
13use Wikimedia\RemexHtml\TreeBuilder\Dispatcher;
14use Wikimedia\RemexHtml\TreeBuilder\TreeBuilder;
15use Wikimedia\RemexHtml\TreeBuilder\TreeMutationTracer;
16
17class RemexPipeline {
18    /** @var Dispatcher */
19    public $dispatcher;
20
21    /** @var TreeBuilder */
22    public $treeBuilder;
23
24    /** @var TreeMutationRelay */
25    private $relay;
26
27    /** @var DOMBuilder */
28    public $domBuilder;
29
30    /** @var Document */
31    public $doc;
32
33    private $nextFakeLength = 1;
34
35    /**
36     * Create a RemexHtml pipeline
37     *
38     * Since we do our own tokenizing, the Dispatcher is called directly to push
39     * data into the pipeline. A RemexHtml Tokenizer is only needed to provide
40     * stub handling for certain TreeBuilder callbacks. Those callbacks are
41     * required by the HTML 5  spec -- we get away with ignoring them because
42     * we are not actually parsing HTML.
43     *
44     * @param Env $env
45     */
46    public function __construct( Env $env ) {
47        $this->domBuilder = new DOMBuilder;
48        $this->relay = new TreeMutationRelay( $this->domBuilder );
49        if ( $env->hasTraceFlag( 'remex' ) ) {
50            $tracer = new TreeMutationTracer(
51                $this->relay,
52                static function ( $msg ) use ( $env ) {
53                    $env->log( 'trace/remex', $msg );
54                }
55            );
56        } else {
57            $tracer = $this->relay;
58        }
59        $this->treeBuilder = new TreeBuilder( $tracer );
60        $this->dispatcher = new Dispatcher( $this->treeBuilder );
61
62        // Create dummy Tokenizer
63        $tokenizer = new Tokenizer( $this->dispatcher, '', [ 'ignoreErrors' => true ] );
64
65        $this->dispatcher->startDocument( $tokenizer, null, null );
66        $this->dispatcher->doctype( 'html', '', '', false, 0, 0 );
67        $this->dispatcher->startTag( 'body', new PlainAttributes(), false, 0, 0 );
68
69        $doc = $this->domBuilder->getFragment();
70        if ( $doc instanceof Document ) {
71            $this->doc = $doc;
72        } else {
73            throw new InternalException( 'Invalid document type' );
74        }
75    }
76
77    /**
78     * Dispatch a meta tag in such a way that it won't be fostered even if the
79     * currently open element is a table. This replaces the old comment hack.
80     *
81     * @param array $attribs
82     */
83    public function insertUnfosteredMeta( array $attribs ) {
84        $this->dispatcher->flushTableText();
85        $this->dispatcher->inHead->startTag(
86            'meta',
87            new Attributes( $this->doc, $attribs ),
88            false, 0, 0
89        );
90    }
91
92    /**
93     * Dispatch a start tag and consider the generated element to be
94     * auto-inserted.
95     *
96     * @param string $name
97     * @param array $attribs
98     * @param bool $selfClose
99     */
100    public function insertImplicitStartTag(
101        string $name, array $attribs, bool $selfClose = false
102    ): void {
103        $attribsObj = new Attributes( $this->doc, $attribs );
104        $this->dispatcher->startTag( $name, $attribsObj, $selfClose, 0, 0 );
105    }
106
107    /**
108     * Dispatch a start tag and consider the generated element to be explicit,
109     * not auto-inserted. Return that element. If no element was created,
110     * return null.
111     *
112     * @param string $name
113     * @param array $attribs
114     * @param bool $selfClose
115     * @return Element|null
116     */
117    public function insertExplicitStartTag(
118        string $name, array $attribs, bool $selfClose = false
119    ): ?Element {
120        $attribsObj = new Attributes( $this->doc, $attribs );
121        $this->relay->matchStartTag( $attribsObj );
122        $this->dispatcher->startTag( $name, $attribsObj, $selfClose, 0, 0 );
123        $element = $this->relay->getMatchedElement();
124        $this->relay->resetMatch();
125        return $element;
126    }
127
128    /**
129     * Dispatch an end tag, and cause the element to be explicitly ended,
130     * i.e. without autoInsertedEnd. Return the element which was ended by the
131     * tag, or null if no element was matched.
132     *
133     * @param string $name
134     * @param bool $isHTML
135     * @return Element|null
136     */
137    public function insertExplicitEndTag(
138        string $name, bool $isHTML
139    ): ?Element {
140        $fakeLength = $this->nextFakeLength++;
141        $this->relay->matchEndTag( $fakeLength, $isHTML );
142        $this->dispatcher->endTag( $name, 0, $fakeLength );
143        $element = $this->relay->getMatchedElement();
144        $this->relay->resetMatch();
145        return $element;
146    }
147
148    /**
149     * Determine if the TreeBuilder is in a fosterable position, i.e. insertion
150     * of a text node will cause fostering of that text.
151     *
152     * @return bool
153     */
154    public function isFosterablePosition() {
155        $openElement = $this->treeBuilder->stack->current;
156        return isset( Consts::$HTML['FosterablePosition'][$openElement->htmlName] );
157    }
158
159}