Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 45 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 1 |
CharInsert | |
0.00% |
0 / 45 |
|
0.00% |
0 / 8 |
156 | |
0.00% |
0 / 1 |
charInsertHook | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
expand | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
processLine | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
armor | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
2 | |||
processItem | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
20 | |||
insertChar | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
getInsertAttribute | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\CharInsert; |
4 | |
5 | use MediaWiki\Parser\Parser; |
6 | use MediaWiki\Parser\Sanitizer; |
7 | use MediaWiki\Xml\Xml; |
8 | |
9 | class CharInsert { |
10 | /** @var array XML-style attributes passed to the tag */ |
11 | private $params; |
12 | /** @var Parser */ |
13 | private $parser; |
14 | |
15 | /** |
16 | * Main entry point, called by the parser. |
17 | * @param string|null $data The textual content of the <charinsert> tag |
18 | * @param array $params XML-style attributes of the <charinsert> tag |
19 | * @param Parser $parser |
20 | * @return string |
21 | */ |
22 | public static function charInsertHook( $data, array $params, Parser $parser ): string { |
23 | if ( $data === null ) { |
24 | return ''; |
25 | } |
26 | |
27 | return ( new self( $params, $parser ) )->expand( $data ); |
28 | } |
29 | |
30 | /** |
31 | * Constructor. |
32 | * @param array $params XML-style attributes of the <charinsert> tag |
33 | * @param Parser $parser |
34 | */ |
35 | private function __construct( array $params, Parser $parser ) { |
36 | $this->params = $params; |
37 | $this->parser = $parser; |
38 | } |
39 | |
40 | /** |
41 | * Parse the content of a whole <charinsert> tag. |
42 | * @param string $data The textual content of the <charinsert> tag |
43 | * @return string HTML to be inserted in the parser output |
44 | */ |
45 | private function expand( $data ): string { |
46 | $data = $this->parser->getStripState()->unstripBoth( $data ); |
47 | $this->parser->getOutput()->addModules( [ 'ext.charinsert' ] ); |
48 | $this->parser->getOutput()->addModuleStyles( [ 'ext.charinsert.styles' ] ); |
49 | return implode( "<br />\n", |
50 | array_map( [ $this, 'processLine' ], |
51 | explode( "\n", trim( $data ) ) ) ); |
52 | } |
53 | |
54 | /** |
55 | * Parse a single line in the <charinsert> tag. |
56 | * @param string $data Textual content of the line |
57 | * @return string HTML to be inserted in the parser output |
58 | */ |
59 | private function processLine( string $data ): string { |
60 | return implode( "\n", |
61 | array_map( [ $this, 'processItem' ], |
62 | preg_split( '/\s+/', $this->armor( $data ) ) ) ); |
63 | } |
64 | |
65 | /** |
66 | * Escape literal whitespace characters in <nowiki> tags. Whitespace |
67 | * within <nowiki> tags is not considered to be an insert boundary. |
68 | * @param string $data Textual content of the line to escape whitespace in |
69 | * @return string The textual content with whitespace escaped and <nowiki> |
70 | * tags removed |
71 | */ |
72 | private function armor( string $data ): string { |
73 | return preg_replace_callback( |
74 | '!<nowiki>(.*?)</nowiki>!i', |
75 | static function ( array $matches ) { |
76 | return strtr( $matches[1], [ |
77 | '\t' => '	', |
78 | '\r' => '', |
79 | ' ' => ' ', |
80 | ] ); |
81 | }, |
82 | $data |
83 | ); |
84 | } |
85 | |
86 | /** |
87 | * Parse a single insert, i.e. largest portion of the input that |
88 | * (after going through armor()) doesn’t contain ASCII whitespace. |
89 | * @param string $data The single insert. |
90 | * @return string HTML to be inserted in the parser output |
91 | */ |
92 | private function processItem( string $data ): string { |
93 | $chars = explode( '+', $data ); |
94 | if ( count( $chars ) > 1 && $chars[0] !== '' ) { |
95 | return $this->insertChar( $chars[0], $chars[1] ); |
96 | } elseif ( count( $chars ) === 1 ) { |
97 | return $this->insertChar( $chars[0] ); |
98 | } else { |
99 | return $this->insertChar( '+' ); |
100 | } |
101 | } |
102 | |
103 | /** |
104 | * Create the HTML for a single insert. |
105 | * @param string $start The start part of the insert, inserted before |
106 | * the text selected by the user (if there is any). |
107 | * @param string $end The end part of the insert, inserted after |
108 | * the text selected by the user (if there is any). |
109 | * @return string HTML to be inserted in the parser output |
110 | */ |
111 | private function insertChar( string $start, string $end = '' ): string { |
112 | $estart = $this->getInsertAttribute( $start ); |
113 | $eend = $this->getInsertAttribute( $end ); |
114 | $inline = $this->params['label'] ?? ( $estart . $eend ); |
115 | |
116 | // Having no href attribute makes the link be more |
117 | // easily copy and pasteable for non-js users. |
118 | return Xml::element( 'a', |
119 | [ |
120 | 'data-mw-charinsert-start' => $estart, |
121 | 'data-mw-charinsert-end' => $eend, |
122 | 'class' => 'mw-charinsert-item' |
123 | ], $inline |
124 | ); |
125 | } |
126 | |
127 | /** |
128 | * Double-escape non-breaking spaces. This allows the user to |
129 | * differentiate the user normal and non-breaking spaces. |
130 | * @param string $text The text to double-escape NBSPs in |
131 | * @return string |
132 | */ |
133 | private function getInsertAttribute( string $text ): string { |
134 | static $invisibles = [ ' ', ' ' ]; |
135 | static $visibles = [ '&nbsp;', '&#160;' ]; |
136 | return Sanitizer::decodeCharReferences( |
137 | str_replace( $invisibles, $visibles, $text ) ); |
138 | } |
139 | } |