Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
55.00% covered (warning)
55.00%
33 / 60
33.33% covered (danger)
33.33%
1 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
Nowiki
55.00% covered (warning)
55.00%
33 / 60
33.33% covered (danger)
33.33%
1 / 3
56.45
0.00% covered (danger)
0.00%
0 / 1
 getConfig
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 sourceToDom
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
4
 domToWikitext
40.00% covered (danger)
40.00%
12 / 30
0.00% covered (danger)
0.00%
0 / 1
63.60
1<?php
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\Ext\Nowiki;
5
6use Wikimedia\Assert\Assert;
7use Wikimedia\Parsoid\DOM\Comment;
8use Wikimedia\Parsoid\DOM\DocumentFragment;
9use Wikimedia\Parsoid\DOM\Element;
10use Wikimedia\Parsoid\DOM\Text;
11use Wikimedia\Parsoid\Ext\DiffDOMUtils;
12use Wikimedia\Parsoid\Ext\DiffUtils;
13use Wikimedia\Parsoid\Ext\DOMDataUtils;
14use Wikimedia\Parsoid\Ext\DOMUtils;
15use Wikimedia\Parsoid\Ext\ExtensionModule;
16use Wikimedia\Parsoid\Ext\ExtensionTagHandler;
17use Wikimedia\Parsoid\Ext\ParsoidExtensionAPI;
18use Wikimedia\Parsoid\Ext\Utils;
19use Wikimedia\Parsoid\NodeData\DataParsoid;
20use Wikimedia\Parsoid\Utils\DOMCompat;
21
22/**
23 * Nowiki treats anything inside it as plain text.
24 */
25class Nowiki extends ExtensionTagHandler implements ExtensionModule {
26
27    /** @inheritDoc */
28    public function getConfig(): array {
29        return [
30            'name' => '<nowiki>',
31            'tags' => [
32                [
33                    'name' => 'nowiki',
34                    'handler' => self::class,
35                ]
36            ]
37        ];
38    }
39
40    /** @inheritDoc */
41    public function sourceToDom(
42        ParsoidExtensionAPI $extApi, string $txt, array $extArgs
43    ): DocumentFragment {
44        $domFragment = $extApi->htmlToDom( '' );
45        $doc = $domFragment->ownerDocument;
46        $span = $doc->createElement( 'span' );
47        DOMUtils::addTypeOf( $span, 'mw:Nowiki' );
48
49        foreach ( preg_split( '/(&[#0-9a-zA-Z]+;)/', $txt, -1, PREG_SPLIT_DELIM_CAPTURE ) as $i => $t ) {
50            if ( $i % 2 === 1 ) {
51                $cc = Utils::decodeWtEntities( $t );
52                if ( $cc !== $t ) {
53                    // This should match the output of the "htmlentity" rule
54                    // in the tokenizer.
55                    $entity = $doc->createElement( 'span' );
56                    DOMUtils::addTypeOf( $entity, 'mw:Entity' );
57                    $dp = new DataParsoid;
58                    $dp->src = $t;
59                    $dp->srcContent = $cc;
60                    DOMDataUtils::setDataParsoid( $entity, $dp );
61                    $entity->appendChild( $doc->createTextNode( $cc ) );
62                    $span->appendChild( $entity );
63                    continue;
64                }
65                // else, fall down there
66            }
67            $span->appendChild( $doc->createTextNode( $t ) );
68        }
69
70        DOMCompat::normalize( $span );
71        $domFragment->appendChild( $span );
72        return $domFragment;
73    }
74
75    /** @inheritDoc */
76    public function domToWikitext(
77        ParsoidExtensionAPI $extApi, Element $node, bool $wrapperUnmodified
78    ) {
79        if ( !$node->hasChildNodes() ) {
80            $extApi->setHtml2wtStateFlag( 'hasSelfClosingNowikis' ); // FIXME
81            return '<nowiki/>';
82        }
83        $str = '<nowiki>';
84        for ( $child = $node->firstChild;  $child;  $child = $child->nextSibling ) {
85            $out = null;
86            if ( $child instanceof Element ) {
87                if ( DiffUtils::isDiffMarker( $child ) ) {
88                    /* ignore */
89                } elseif ( DOMCompat::nodeName( $child ) === 'span' &&
90                    DOMUtils::hasTypeOf( $child, 'mw:Entity' ) &&
91                    DiffDOMUtils::hasNChildren( $child, 1 )
92                ) {
93                    $dp = DOMDataUtils::getDataParsoid( $child );
94                    if ( isset( $dp->src ) && $dp->srcContent === $child->textContent ) {
95                        // Unedited content
96                        $out = $dp->src;
97                    } else {
98                        // Edited content
99                        $out = Utils::entityEncodeAll( $child->firstChild->nodeValue );
100                    }
101                // DisplaySpace is added in a final post-processing pass so,
102                // even though it isn't emitted in the extension handler, we
103                // need to deal with the possibility of its presence
104                // FIXME(T254501): Should avoid the need for this
105                } elseif (
106                    DOMCompat::nodeName( $child ) === 'span' &&
107                    DOMUtils::hasTypeOf( $child, 'mw:DisplaySpace' ) &&
108                    DiffDOMUtils::hasNChildren( $child, 1 )
109                ) {
110                    $out = ' ';
111                } else {
112                    /* This is a hacky fallback for what is essentially
113                     * undefined behavior. No matter what we emit here,
114                     * this won't roundtrip html2html. */
115                    $extApi->log( 'error/html2wt/nowiki', 'Invalid nowiki content' );
116                    $out = $child->textContent;
117                }
118            } elseif ( $child instanceof Text ) {
119                $out = $child->nodeValue;
120            } else {
121                Assert::invariant( $child instanceof Comment, "Expected a comment here" );
122                /* Comments can't be embedded in a <nowiki> */
123                $extApi->log( 'error/html2wt/nowiki',
124                    'Discarded invalid embedded comment in a <nowiki>' );
125                $out = '';
126            }
127
128            // Always escape any nowikis found in $out
129            if ( $out ) {
130                // Inlined helper that previously existed in Parsoid's WT Utils
131                $str .= preg_replace( '#<(/?nowiki\s*/?\s*)>#i', '&lt;$1&gt;', $out );
132            }
133        }
134
135        return $str . '</nowiki>';
136    }
137
138}