summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/Scribunto/includes/engines/LuaCommon/TextLibrary.php
blob: 794d382192c56ea5bbce6aa1fbf8eb3ff14b63f9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
<?php

// @codingStandardsIgnoreLine Squiz.Classes.ValidClassName.NotCamelCaps
class Scribunto_LuaTextLibrary extends Scribunto_LuaLibraryBase {
	// Matches Lua mw.text constants
	const JSON_PRESERVE_KEYS = 1;
	const JSON_TRY_FIXING = 2;
	const JSON_PRETTY = 4;

	function register() {
		global $wgUrlProtocols;

		$lib = [
			'unstrip' => [ $this, 'textUnstrip' ],
			'unstripNoWiki' => [ $this, 'textUnstripNoWiki' ],
			'killMarkers' => [ $this, 'killMarkers' ],
			'getEntityTable' => [ $this, 'getEntityTable' ],
			'jsonEncode' => [ $this, 'jsonEncode' ],
			'jsonDecode' => [ $this, 'jsonDecode' ],
		];
		$opts = [
			'comma' => wfMessage( 'comma-separator' )->inContentLanguage()->text(),
			'and' => wfMessage( 'and' )->inContentLanguage()->text() .
				wfMessage( 'word-separator' )->inContentLanguage()->text(),
			'ellipsis' => wfMessage( 'ellipsis' )->inContentLanguage()->text(),
			'nowiki_protocols' => [],
		];

		foreach ( $wgUrlProtocols as $prot ) {
			if ( substr( $prot, -1 ) === ':' ) {
				// To convert the protocol into a case-insensitive Lua pattern,
				// we need to replace letters with a character class like [Xx]
				// and insert a '%' before various punctuation.
				$prot = preg_replace_callback( '/([a-zA-Z])|([()^$%.\[\]*+?-])/', function ( $m ) {
					if ( $m[1] ) {
						return '[' . strtoupper( $m[1] ) . strtolower( $m[1] ) . ']';
					} else {
						return '%' . $m[2];
					}
				}, substr( $prot, 0, -1 ) );
				$opts['nowiki_protocols']["($prot):"] = '%1&#58;';
			}
		}

		return $this->getEngine()->registerInterface( 'mw.text.lua', $lib, $opts );
	}

	function textUnstrip( $s ) {
		$this->checkType( 'unstrip', 1, $s, 'string' );
		$stripState = $this->getParser()->mStripState;
		return [ $stripState->killMarkers( $stripState->unstripNoWiki( $s ) ) ];
	}

	function textUnstripNoWiki( $s ) {
		$this->checkType( 'unstripNoWiki', 1, $s, 'string' );
		return [ $this->getParser()->mStripState->unstripNoWiki( $s ) ];
	}

	function killMarkers( $s ) {
		$this->checkType( 'killMarkers', 1, $s, 'string' );
		return [ $this->getParser()->mStripState->killMarkers( $s ) ];
	}

	function getEntityTable() {
		$table = array_flip(
			get_html_translation_table( HTML_ENTITIES, ENT_QUOTES | ENT_HTML5, "UTF-8" )
		);
		return [ $table ];
	}

	public function jsonEncode( $value, $flags ) {
		$this->checkTypeOptional( 'mw.text.jsonEncode', 2, $flags, 'number', 0 );
		$flags = (int)$flags;
		if ( !( $flags & self::JSON_PRESERVE_KEYS ) && is_array( $value ) ) {
			$value = self::reindexArrays( $value, true );
		}
		$ret = FormatJson::encode( $value, (bool)( $flags & self::JSON_PRETTY ), FormatJson::ALL_OK );
		if ( $ret === false ) {
			throw new Scribunto_LuaError( 'mw.text.jsonEncode: Unable to encode value' );
		}
		return [ $ret ];
	}

	public function jsonDecode( $s, $flags ) {
		$this->checkType( 'mw.text.jsonDecode', 1, $s, 'string' );
		$this->checkTypeOptional( 'mw.text.jsonDecode', 2, $flags, 'number', 0 );
		$flags = (int)$flags;
		$opts = FormatJson::FORCE_ASSOC;
		if ( $flags & self::JSON_TRY_FIXING ) {
			$opts |= FormatJson::TRY_FIXING;
		}
		$status = FormatJson::parse( $s, $opts );
		if ( !$status->isOk() ) {
			throw new Scribunto_LuaError( 'mw.text.jsonDecode: ' . $status->getMessage()->text() );
		}
		$val = $status->getValue();
		if ( !( $flags & self::JSON_PRESERVE_KEYS ) && is_array( $val ) ) {
			$val = self::reindexArrays( $val, false );
		}
		return [ $val ];
	}

	/** Recursively reindex array with integer keys to 0-based or 1-based
	 * @param array $arr
	 * @param bool $isEncoding
	 * @return array
	 */
	private static function reindexArrays( array $arr, $isEncoding ) {
		if ( $isEncoding ) {
			ksort( $arr, SORT_NUMERIC );
			$next = 1;
		} else {
			$next = 0;
		}
		$isSequence = true;
		foreach ( $arr as $k => &$v ) {
			if ( is_array( $v ) ) {
				$v = self::reindexArrays( $v, $isEncoding );
			}

			if ( $isSequence ) {
				if ( is_int( $k ) ) {
					$isSequence = $next++ === $k;
				} elseif ( $isEncoding && ctype_digit( $k ) ) {
					// json_decode currently doesn't return integer keys for {}
					$isSequence = $next++ === (int)$k;
				} else {
					$isSequence = false;
				}
			}
		}
		if ( $isSequence ) {
			if ( $isEncoding ) {
				return array_values( $arr );
			} else {
				return $arr ? array_combine( range( 1, count( $arr ) ), $arr ) : $arr;
			}
		}
		return $arr;
	}

}