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