Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
QuoteHandler
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 3
72
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 handle
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 precedingQuoteEltRequiresEscape
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
20
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\DiffDOMUtils;
10use Wikimedia\Parsoid\Utils\DOMUtils;
11
12class QuoteHandler extends DOMHandler {
13
14    /** @var string Quote sequence to match as opener/closer */
15    public $quotes;
16
17    /**
18     * @param string $quotes Quote sequence to match as opener/closer
19     */
20    public function __construct( string $quotes ) {
21        parent::__construct( false );
22        $this->quotes = $quotes;
23    }
24
25    /** @inheritDoc */
26    public function handle(
27        Element $node, SerializerState $state, bool $wrapperUnmodified = false
28    ): ?Node {
29        if ( $this->precedingQuoteEltRequiresEscape( $node ) ) {
30            $state->emitChunk( '<nowiki/>', $node );
31        }
32        $state->emitChunk( $this->quotes, $node );
33
34        if ( $node->hasChildNodes() ) {
35            $state->serializeChildren( $node );
36        } else {
37            // Empty nodes like <i></i> or <b></b> need
38            // a <nowiki/> in place of the empty content so that
39            // they parse back identically.
40            $state->emitChunk( '<nowiki/>', $node );
41        }
42
43        $state->emitChunk( $this->quotes, $node );
44        return $node->nextSibling;
45    }
46
47    private function precedingQuoteEltRequiresEscape(
48        Element $node
49    ): bool {
50        // * <i> and <b> siblings don't need a <nowiki/> separation
51        // as long as quote chars in text nodes are always
52        // properly escaped -- which they are right now.
53        //
54        // * Adjacent quote siblings need a <nowiki/> separation
55        // between them if either of them will individually
56        // generate a sequence of quotes 4 or longer. That can
57        // only happen when either prev or node is of the form:
58        // <i><b>...</b></i>
59        //
60        // For new/edited DOMs, this can never happen because
61        // wts.minimizeQuoteTags.js does quote tag minimization.
62        //
63        // For DOMs from existing wikitext, this can only happen
64        // because of auto-inserted end/start tags. (Ex: ''a''' b ''c''')
65        $prev = DiffDOMUtils::previousNonDeletedSibling( $node );
66        return $prev && DOMUtils::isQuoteElt( $prev )
67            && ( DOMUtils::isQuoteElt( DiffDOMUtils::lastNonDeletedChild( $prev ) )
68                || DOMUtils::isQuoteElt( DiffDOMUtils::firstNonDeletedChild( $node ) ) );
69    }
70
71}