Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 14 |
|
0.00% |
0 / 3 |
CRAP | |
0.00% |
0 / 1 |
| QuoteHandler | |
0.00% |
0 / 14 |
|
0.00% |
0 / 3 |
72 | |
0.00% |
0 / 1 |
| __construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
| handle | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
| precedingQuoteEltRequiresEscape | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
20 | |||
| 1 | <?php |
| 2 | declare( strict_types = 1 ); |
| 3 | |
| 4 | namespace Wikimedia\Parsoid\Html2Wt\DOMHandlers; |
| 5 | |
| 6 | use Wikimedia\Parsoid\DOM\Element; |
| 7 | use Wikimedia\Parsoid\DOM\Node; |
| 8 | use Wikimedia\Parsoid\Html2Wt\SerializerState; |
| 9 | use Wikimedia\Parsoid\Utils\DiffDOMUtils; |
| 10 | use Wikimedia\Parsoid\Utils\DOMUtils; |
| 11 | |
| 12 | class 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 | } |