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