Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
BRHandler
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 6
420
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
 handle
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 before
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
20
 after
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
42
 isPbr
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 isPbrP
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\Html2Wt\DOMHandlers;
5
6use Wikimedia\Parsoid\DOM\Element;
7use Wikimedia\Parsoid\DOM\Node;
8use Wikimedia\Parsoid\Html2Wt\SerializerState;
9use Wikimedia\Parsoid\Utils\DOMCompat;
10use Wikimedia\Parsoid\Utils\DOMDataUtils;
11use Wikimedia\Parsoid\Utils\DOMUtils;
12
13class BRHandler extends DOMHandler {
14
15    public function __construct() {
16        parent::__construct( false );
17    }
18
19    /** @inheritDoc */
20    public function handle(
21        Element $node, SerializerState $state, bool $wrapperUnmodified = false
22    ): ?Node {
23        if ( $state->singleLineContext->enforced()
24             || ( DOMDataUtils::getDataParsoid( $node )->stx ?? null ) === 'html'
25             || DOMCompat::nodeName( $node->parentNode ) !== 'p'
26        ) {
27            // <br/> has special newline-based semantics in
28            // parser-generated <p><br/>.. HTML
29            $state->emitChunk( '<br />', $node );
30        }
31
32        // If P_BR (or P_BR_P), dont emit anything for the <br> so that
33        // constraints propagate to the next node that emits content.
34        return $node->nextSibling;
35    }
36
37    /** @inheritDoc */
38    public function before( Element $node, Node $otherNode, SerializerState $state ): array {
39        if ( $state->singleLineContext->enforced() || !$this->isPbr( $node ) ) {
40            return [];
41        }
42
43        $c = $state->sep->constraints ?: [ 'min' => 0 ];
44        // <h2>..</h2><p><br/>..
45        // <p>..</p><p><br/>..
46        // In all cases, we need at least 3 newlines before
47        // any content that follows the <br/>.
48        // Whether we need 4 depends what comes after <br/>.
49        // content or a </p>. The after handler deals with it.
50        return [ 'min' => max( 3, $c['min'] + 1 ) ];
51    }
52
53    /**
54     * @inheritDoc
55     * NOTE: There is an asymmetry in the before/after handlers.
56     */
57    public function after( Element $node, Node $otherNode, SerializerState $state ): array {
58        // Note that the before handler has already forced 1 additional
59        // newline for all <p><br/> scenarios which simplifies the work
60        // of the after handler.
61        //
62        // Nothing changes with constraints if we are not
63        // in a P-P transition. <br/> has special newline-based
64        // semantics only in a parser-generated <p><br/>.. HTML.
65
66        if ( $state->singleLineContext->enforced()
67             || !PHandler::isPPTransition( DOMUtils::nextNonSepSibling( $node->parentNode ) )
68        ) {
69            return [];
70        }
71
72        $c = $state->sep->constraints ?: [ 'min' => 0 ];
73        if ( $this->isPbrP( $node ) ) {
74            // The <br/> forces an additional newline when part of
75            // a <p><br/></p>.
76            //
77            // Ex: <p><br/></p><p>..</p> => at least 4 newlines before
78            // content of the *next* p-tag.
79            return [ 'min' => max( 4, $c['min'] + 1 ) ];
80        } elseif ( $this->isPbr( $node ) ) {
81            // Since the <br/> is followed by content, the newline
82            // constraint isn't bumped.
83            //
84            // Ex: <p><br/>..<p><p>..</p> => at least 2 newlines after
85            // content of *this* p-tag
86            return [ 'min' => max( 2, $c['min'] ) ];
87        }
88
89        return [];
90    }
91
92    /**
93     * @param Element $br
94     * @return bool
95     */
96    private function isPbr( Element $br ): bool {
97        return ( DOMDataUtils::getDataParsoid( $br )->stx ?? null ) !== 'html'
98            && DOMCompat::nodeName( $br->parentNode ) === 'p'
99            && DOMUtils::firstNonSepChild( $br->parentNode ) === $br;
100    }
101
102    /**
103     * @param Element $br
104     * @return bool
105     */
106    private function isPbrP( Element $br ): bool {
107        return $this->isPbr( $br ) && DOMUtils::nextNonSepSibling( $br ) === null;
108    }
109
110}