Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 54 |
|
0.00% |
0 / 5 |
CRAP | |
0.00% |
0 / 1 |
Hooks | |
0.00% |
0 / 54 |
|
0.00% |
0 / 5 |
552 | |
0.00% |
0 / 1 |
onParserFirstCallInit | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
42 | |||
checkOverrideParam | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
checkJosaonlyParam | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
getJosa | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
42 | |||
convertToJohabCode | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
56 |
1 | <?php |
2 | /** |
3 | * Extension:Josa |
4 | * Author: Bae Junehyeon <devunt@gmail.com> |
5 | * Original implementation by Park Shinjo <peremen@gmail.com> |
6 | * License: MIT License |
7 | */ |
8 | |
9 | namespace MediaWiki\Extension\Josa; |
10 | |
11 | use MediaWiki\Hook\ParserFirstCallInitHook; |
12 | use Parser; |
13 | |
14 | class Hooks implements ParserFirstCallInitHook { |
15 | /** @var array */ |
16 | private static $josaMap = [ |
17 | 'Eul/Ruel' => [ '을', '를', '을(를)' ], // 곶감을 / 사과를 |
18 | 'Eun/Neun' => [ '은', '는', '은(는)' ], // 곶감은 / 사과는 |
19 | 'E/Ga' => [ '이', '가', '이(가)' ], // 곶감이 / 사과가 |
20 | 'Gwa/Wa' => [ '과', '와', '과(와)' ], // 곶감과 / 사과와 |
21 | 'A/Ya' => [ '아', '야', '아(야)' ], // 태준아 / 철수야 |
22 | 'Euro/Ro' => [ '으로', '로', '(으)로' ], // 집으로 / 학교로 |
23 | 'E/' => [ '이', '', '(이)' ], // 태준이가 / 철수가 |
24 | ]; |
25 | |
26 | /** |
27 | * This dictionary matches each numbers and alphabets to the corresponding hangul pronounces. |
28 | * |
29 | * @var array |
30 | */ |
31 | private static $pronounceMap = [ |
32 | '0' => '영', '1' => '일', '2' => '이', |
33 | '3' => '삼', '4' => '사', '5' => '오', |
34 | '6' => '육', '7' => '칠', '8' => '팔', |
35 | '9' => '구', |
36 | |
37 | 'A' => '이', 'B' => '비', 'C' => '씨', |
38 | 'D' => '디', 'E' => '이', 'F' => '프', |
39 | 'G' => '지', 'H' => '치', 'I' => '이', |
40 | 'J' => '이', 'K' => '이', 'L' => '엘', |
41 | 'M' => '엠', 'N' => '엔', 'O' => '오', |
42 | 'P' => '피', 'Q' => '큐', 'R' => '알', |
43 | 'S' => '스', 'T' => '티', 'U' => '유', |
44 | 'V' => '이', 'W' => '유', 'X' => '스', |
45 | 'Y' => '이', 'Z' => '지', |
46 | ]; |
47 | |
48 | /** |
49 | * @param Parser $parser |
50 | */ |
51 | public function onParserFirstCallInit( $parser ) { |
52 | foreach ( self::$josaMap as $key => $value ) { |
53 | $parser->setFunctionHook( $key, static function ( |
54 | $parser, $str, $param1 = null, $param2 = null |
55 | ) use ( $key ) { |
56 | $josa = self::getJosa( $key, $str ); |
57 | |
58 | foreach ( [ $param1, $param2 ] as $param ) { |
59 | if ( $param === null ) { |
60 | break; |
61 | } |
62 | |
63 | $override = self::checkOverrideParam( $param ); |
64 | if ( $override !== false ) { |
65 | $josa = $override; |
66 | continue; |
67 | } |
68 | |
69 | $josaonly = self::checkJosaonlyParam( $param ); |
70 | if ( $josaonly === true ) { |
71 | $str = ''; |
72 | continue; |
73 | } |
74 | } |
75 | |
76 | return $str . $josa; |
77 | } ); |
78 | } |
79 | } |
80 | |
81 | /** |
82 | * @param string $param String to check |
83 | * @return bool|string Value of override param or false if it isn't presented |
84 | */ |
85 | public static function checkOverrideParam( $param ) { |
86 | if ( preg_match( '/^(override|덮어쓰기|오버라이드)=(.*)$/', $param, $matches ) === 0 ) { |
87 | return false; |
88 | } |
89 | return $matches[2]; |
90 | } |
91 | |
92 | /** |
93 | * @param string $param String to check |
94 | * @return bool Existence of josaonly param |
95 | */ |
96 | public static function checkJosaonlyParam( $param ) { |
97 | return ( $param === 'josaonly' || $param === '조사만' ); |
98 | } |
99 | |
100 | /** |
101 | * @param string $type Type of the last letter in the word (see JosaHooks::$josaMap's keys) |
102 | * @param string $str Word to determine the josa |
103 | * @return string Josa |
104 | */ |
105 | public static function getJosa( $type, $str ) { |
106 | $str = preg_replace( |
107 | '/[\x{0000}-\x{0027}\x{002A}-\x{002F}\x{003A}-\x{0040}\x{005B}-\x{0060}\x{007B}-\x{00FF}]/u', |
108 | '', |
109 | $str |
110 | ); |
111 | $chr = mb_substr( $str, -1, 1, 'utf-8' ); |
112 | if ( array_key_exists( $chr, self::$pronounceMap ) ) { |
113 | $chr = self::$pronounceMap[$chr]; |
114 | } |
115 | $code = self::convertToJohabCode( $chr ); |
116 | if ( !$code ) { |
117 | // Not hangul |
118 | $idx = 2; |
119 | } elseif ( ( $code - 0xAC00 ) % 28 === 0 ) { |
120 | // No trailing consonant |
121 | $idx = 1; |
122 | } elseif ( ( $type === 'Euro/Ro' ) && ( ( $code - 0xAC00 ) % 28 === 8 ) ) { |
123 | // $type is Euro/Ro and trailing consonant is rieul(ㄹ). |
124 | // This is an exception on Korean postposition rules. |
125 | $idx = 1; |
126 | } else { |
127 | // Trailing consonant exists |
128 | $idx = 0; |
129 | } |
130 | return self::$josaMap[$type][$idx]; |
131 | } |
132 | |
133 | /** |
134 | * @see https://ko.wikipedia.org/wiki/%ED%95%9C%EA%B8%80_%EC%83%81%EC%9A%A9_%EC%A1%B0%ED%95%A9%ED%98%95_%EC%9D%B8%EC%BD%94%EB%94%A9 |
135 | * |
136 | * @param string $str String to convert |
137 | * @return int|bool Converted Johab code |
138 | */ |
139 | private static function convertToJohabCode( $str ) { |
140 | $values = []; |
141 | $lookingFor = 1; |
142 | for ( $i = 0, $len = strlen( $str ); $i < $len; $i++ ) { |
143 | $thisValue = ord( $str[ $i ] ); |
144 | if ( $thisValue < 128 ) { |
145 | return false; |
146 | } else { |
147 | if ( count( $values ) === 0 ) { |
148 | $lookingFor = ( $thisValue < 224 ) ? 2 : 3; |
149 | } |
150 | $values[] = $thisValue; |
151 | if ( count( $values ) === $lookingFor ) { |
152 | $number = ( $lookingFor === 3 ) ? |
153 | ( ( $values[0] % 16 ) * 4096 ) + ( ( $values[1] % 64 ) * 64 ) + ( $values[2] % 64 ) : |
154 | ( ( $values[0] % 32 ) * 64 ) + ( $values[1] % 64 ); |
155 | return $number; |
156 | } |
157 | } |
158 | } |
159 | return false; |
160 | } |
161 | } |