Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 52
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpanHandler
0.00% covered (danger)
0.00%
0 / 52
0.00% covered (danger)
0.00%
0 / 3
506
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 / 44
0.00% covered (danger)
0.00%
0 / 1
420
 isRecognizedSpanWrapper
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\Html2Wt\DOMHandlers;
5
6use Wikimedia\Parsoid\Core\MediaStructure;
7use Wikimedia\Parsoid\DOM\Element;
8use Wikimedia\Parsoid\DOM\Node;
9use Wikimedia\Parsoid\DOM\Text;
10use Wikimedia\Parsoid\Html2Wt\LinkHandlerUtils;
11use Wikimedia\Parsoid\Html2Wt\SerializerState;
12use Wikimedia\Parsoid\Html2Wt\WTSUtils;
13use Wikimedia\Parsoid\Tokens\KV;
14use Wikimedia\Parsoid\Utils\DiffDOMUtils;
15use Wikimedia\Parsoid\Utils\DOMCompat;
16use Wikimedia\Parsoid\Utils\DOMDataUtils;
17use Wikimedia\Parsoid\Utils\DOMUtils;
18use Wikimedia\Parsoid\Utils\Utils;
19use Wikimedia\Parsoid\Utils\WTUtils;
20
21class SpanHandler extends DOMHandler {
22
23    public function __construct() {
24        parent::__construct( false );
25    }
26
27    /** @inheritDoc */
28    public function handle(
29        Element $node, SerializerState $state, bool $wrapperUnmodified = false
30    ): ?Node {
31        $env = $state->getEnv();
32        $dp = DOMDataUtils::getDataParsoid( $node );
33        if ( self::isRecognizedSpanWrapper( $node ) ) {
34            if ( DOMUtils::hasTypeOf( $node, 'mw:Nowiki' ) ) {
35                $ext = $env->getSiteConfig()->getExtTagImpl( 'nowiki' );
36                $src = $ext->domToWikitext( $state->extApi, $node, $wrapperUnmodified );
37                $state->singleLineContext->disable();
38                $state->serializer->emitWikitext( $src, $node );
39                $state->singleLineContext->pop();
40            } elseif ( WTUtils::isInlineMedia( $node ) ) {
41                LinkHandlerUtils::figureHandler(
42                    $state, $node, MediaStructure::parse( $node )
43                );
44            } elseif (
45                DOMUtils::hasTypeOf( $node, 'mw:Entity' ) &&
46                DiffDOMUtils::hasNChildren( $node, 1 )
47            ) {
48                $contentSrc = ( $node->textContent != '' ) ? $node->textContent
49                    : DOMCompat::getInnerHTML( $node );
50                // handle a new mw:Entity (not handled by selser) by
51                // serializing its children
52                if ( isset( $dp->src ) && $contentSrc === ( $dp->srcContent ?? null ) ) {
53                    $state->serializer->emitWikitext( $dp->src, $node );
54                } elseif ( $node->firstChild instanceof Text ) {
55                    $state->emitChunk(
56                        Utils::entityEncodeAll( $node->firstChild->nodeValue ),
57                        $node->firstChild );
58                } else {
59                    $state->serializeChildren( $node );
60                }
61            } elseif ( DOMUtils::hasTypeOf( $node, 'mw:DisplaySpace' ) ) {
62                // FIXME(T254501): Throw an UnreachableException here instead
63                $state->emitChunk( ' ', $node );
64            } elseif ( DOMUtils::matchTypeOf( $node, '#^mw:Placeholder(/|$)#' ) ) {
65                if ( isset( $dp->src ) ) {
66                    $this->emitPlaceholderSrc( $node, $state );
67                    return $node->nextSibling;
68                } else {
69                    ( new FallbackHTMLHandler )->handle( $node, $state );
70                }
71            }
72        } elseif ( $node->hasAttribute( 'data-mw-selser-wrapper' ) ) {
73            $state->serializeChildren( $node );
74        } else {
75            $kvs = array_filter( WTSUtils::getAttributeKVArray( $node ), static function ( KV $kv ) {
76                return !preg_match( '/^data-parsoid/', $kv->k )
77                    && ( $kv->k !== DOMDataUtils::DATA_OBJECT_ATTR_NAME )
78                    && !( $kv->k === 'id' && preg_match( '/^mw[\w-]{2,}$/D', $kv->v ) );
79            } );
80            if ( !empty( $dp->misnested ) && ( $dp->stx ?? null ) !== 'html'
81                && !count( $kvs )
82            ) {
83                // Discard span wrappers added to flag misnested content.
84                // Warn since selser should have reused source.
85                $env->log( 'warn', 'Serializing misnested content: ' . DOMCompat::getOuterHTML( $node ) );
86                $state->serializeChildren( $node );
87            } else {
88                // Fall back to plain HTML serialization for spans created
89                // by the editor.
90                ( new FallbackHTMLHandler )->handle( $node, $state );
91            }
92        }
93        return $node->nextSibling;
94    }
95
96    private static function isRecognizedSpanWrapper( Element $node ): ?string {
97        return DOMUtils::matchTypeOf(
98            $node,
99            // FIXME(T254501): Remove mw:DisplaySpace
100            // TODO: Remove "Image|Video|Audio" when version 2.4.0 of the content
101            // is no longer supported
102            '#^mw:('
103                . 'Nowiki|Entity|DisplaySpace|Placeholder(/\w+)?'
104                . '|(File|Image|Video|Audio)(/(Frameless|Frame|Thumb))?'
105                . ')$#'
106        );
107    }
108
109}