merging latest master
[lhc/web/wiklou.git] / resources / mediawiki / mediawiki.Title.js
1 /**
2 * mediaWiki.Title
3 *
4 * @author Neil Kandalgaonkar, 2010
5 * @author Timo Tijhof, 2011
6 * @since 1.18
7 *
8 * Relies on: mw.config (wgFormattedNamespaces, wgNamespaceIds, wgCaseSensitiveNamespaces), mw.util.wikiGetlink
9 */
10 ( function ( mw, $ ) {
11
12 /* Local space */
13
14 /**
15 * Title
16 * @constructor
17 *
18 * @param title {String} Title of the page. If no second argument given,
19 * this will be searched for a namespace.
20 * @param namespace {Number} (optional) Namespace id. If given, title will be taken as-is.
21 * @return {Title} this
22 */
23 function Title( title, namespace ) {
24 this.ns = 0; // integer namespace id
25 this.name = null; // name in canonical 'database' form
26 this.ext = null; // extension
27
28 if ( arguments.length === 2 ) {
29 setNameAndExtension( this, title );
30 this.ns = fixNsId( namespace );
31 } else if ( arguments.length === 1 ) {
32 setAll( this, title );
33 }
34 return this;
35 }
36
37 var
38 /**
39 * Strip some illegal chars: control chars, colon, less than, greater than,
40 * brackets, braces, pipe, whitespace and normal spaces. This still leaves some insanity
41 * intact, like unicode bidi chars, but it's a good start..
42 * @param s {String}
43 * @return {String}
44 */
45 clean = function ( s ) {
46 if ( s !== undefined ) {
47 return s.replace( /[\x00-\x1f\x23\x3c\x3e\x5b\x5d\x7b\x7c\x7d\x7f\s]+/g, '_' );
48 }
49 },
50
51 /**
52 * Convert db-key to readable text.
53 * @param s {String}
54 * @return {String}
55 */
56 text = function ( s ) {
57 if ( s !== null && s !== undefined ) {
58 return s.replace( /_/g, ' ' );
59 } else {
60 return '';
61 }
62 },
63
64 /**
65 * Sanitize name.
66 */
67 fixName = function ( s ) {
68 return clean( $.trim( s ) );
69 },
70
71 /**
72 * Sanitize name.
73 */
74 fixExt = function ( s ) {
75 return clean( s );
76 },
77
78 /**
79 * Sanitize namespace id.
80 * @param id {Number} Namespace id.
81 * @return {Number|Boolean} The id as-is or boolean false if invalid.
82 */
83 fixNsId = function ( id ) {
84 // wgFormattedNamespaces is an object of *string* key-vals (ie. arr["0"] not arr[0] )
85 var ns = mw.config.get( 'wgFormattedNamespaces' )[id.toString()];
86
87 // Check only undefined (may be false-y, such as '' (main namespace) ).
88 if ( ns === undefined ) {
89 return false;
90 } else {
91 return Number( id );
92 }
93 },
94
95 /**
96 * Get namespace id from namespace name by any known namespace/id pair (localized, canonical or alias).
97 *
98 * @example On a German wiki this would return 6 for any of 'File', 'Datei', 'Image' or even 'Bild'.
99 * @param ns {String} Namespace name (case insensitive, leading/trailing space ignored).
100 * @return {Number|Boolean} Namespace id or boolean false if unrecognized.
101 */
102 getNsIdByName = function ( ns ) {
103 // Don't cast non-strings to strings, because null or undefined
104 // should not result in returning the id of a potential namespace
105 // called "Null:" (e.g. on nullwiki.example.org)
106 // Also, toLowerCase throws exception on null/undefined, because
107 // it is a String.prototype method.
108 if ( typeof ns !== 'string' ) {
109 return false;
110 }
111 ns = clean( $.trim( ns.toLowerCase() ) ); // Normalize
112 var id = mw.config.get( 'wgNamespaceIds' )[ns];
113 if ( id === undefined ) {
114 mw.log( 'mw.Title: Unrecognized namespace: ' + ns );
115 return false;
116 }
117 return fixNsId( id );
118 },
119
120 /**
121 * Helper to extract namespace, name and extension from a string.
122 *
123 * @param title {mw.Title}
124 * @param raw {String}
125 * @return {mw.Title}
126 */
127 setAll = function ( title, s ) {
128 // In normal browsers the match-array contains null/undefined if there's no match,
129 // IE returns an empty string.
130 var matches = s.match( /^(?:([^:]+):)?(.*?)(?:\.(\w{1,5}))?$/ ),
131 ns_match = getNsIdByName( matches[1] );
132
133 // Namespace must be valid, and title must be a non-empty string.
134 if ( ns_match && typeof matches[2] === 'string' && matches[2] !== '' ) {
135 title.ns = ns_match;
136 title.name = fixName( matches[2] );
137 if ( typeof matches[3] === 'string' && matches[3] !== '' ) {
138 title.ext = fixExt( matches[3] );
139 }
140 } else {
141 // Consistency with MediaWiki PHP: Unknown namespace -> fallback to main namespace.
142 title.ns = 0;
143 setNameAndExtension( title, s );
144 }
145 return title;
146 },
147
148 /**
149 * Helper to extract name and extension from a string.
150 *
151 * @param title {mw.Title}
152 * @param raw {String}
153 * @return {mw.Title}
154 */
155 setNameAndExtension = function ( title, raw ) {
156 // In normal browsers the match-array contains null/undefined if there's no match,
157 // IE returns an empty string.
158 var matches = raw.match( /^(?:)?(.*?)(?:\.(\w{1,5}))?$/ );
159
160 // Title must be a non-empty string.
161 if ( typeof matches[1] === 'string' && matches[1] !== '' ) {
162 title.name = fixName( matches[1] );
163 if ( typeof matches[2] === 'string' && matches[2] !== '' ) {
164 title.ext = fixExt( matches[2] );
165 }
166 } else {
167 throw new Error( 'mw.Title: Could not parse title "' + raw + '"' );
168 }
169 return title;
170 };
171
172
173 /* Static space */
174
175 /**
176 * Whether this title exists on the wiki.
177 * @param title {mixed} prefixed db-key name (string) or instance of Title
178 * @return {mixed} Boolean true/false if the information is available. Otherwise null.
179 */
180 Title.exists = function ( title ) {
181 var type = $.type( title ), obj = Title.exist.pages, match;
182 if ( type === 'string' ) {
183 match = obj[title];
184 } else if ( type === 'object' && title instanceof Title ) {
185 match = obj[title.toString()];
186 } else {
187 throw new Error( 'mw.Title.exists: title must be a string or an instance of Title' );
188 }
189 if ( typeof match === 'boolean' ) {
190 return match;
191 }
192 return null;
193 };
194
195 /**
196 * @var Title.exist {Object}
197 */
198 Title.exist = {
199 /**
200 * @var Title.exist.pages {Object} Keyed by PrefixedDb title.
201 * Boolean true value indicates page does exist.
202 */
203 pages: {},
204 /**
205 * @example Declare existing titles: Title.exist.set(['User:John_Doe', ...]);
206 * @example Declare titles nonexistent: Title.exist.set(['File:Foo_bar.jpg', ...], false);
207 * @param titles {String|Array} Title(s) in strict prefixedDb title form.
208 * @param state {Boolean} (optional) State of the given titles. Defaults to true.
209 * @return {Boolean}
210 */
211 set: function ( titles, state ) {
212 titles = $.isArray( titles ) ? titles : [titles];
213 state = state === undefined ? true : !!state;
214 var pages = this.pages, i, len = titles.length;
215 for ( i = 0; i < len; i++ ) {
216 pages[ titles[i] ] = state;
217 }
218 return true;
219 }
220 };
221
222 /* Public methods */
223
224 var fn = {
225 constructor: Title,
226
227 /**
228 * Get the namespace number.
229 * @return {Number}
230 */
231 getNamespaceId: function (){
232 return this.ns;
233 },
234
235 /**
236 * Get the namespace prefix (in the content-language).
237 * In NS_MAIN this is '', otherwise namespace name plus ':'
238 * @return {String}
239 */
240 getNamespacePrefix: function (){
241 return mw.config.get( 'wgFormattedNamespaces' )[this.ns].replace( / /g, '_' ) + (this.ns === 0 ? '' : ':');
242 },
243
244 /**
245 * The name, like "Foo_bar"
246 * @return {String}
247 */
248 getName: function () {
249 if ( $.inArray( this.ns, mw.config.get( 'wgCaseSensitiveNamespaces' ) ) !== -1 ) {
250 return this.name;
251 } else {
252 return $.ucFirst( this.name );
253 }
254 },
255
256 /**
257 * The name, like "Foo bar"
258 * @return {String}
259 */
260 getNameText: function () {
261 return text( this.getName() );
262 },
263
264 /**
265 * Get full name in prefixed DB form, like File:Foo_bar.jpg,
266 * most useful for API calls, anything that must identify the "title".
267 */
268 getPrefixedDb: function () {
269 return this.getNamespacePrefix() + this.getMain();
270 },
271
272 /**
273 * Get full name in text form, like "File:Foo bar.jpg".
274 * @return {String}
275 */
276 getPrefixedText: function () {
277 return text( this.getPrefixedDb() );
278 },
279
280 /**
281 * The main title (without namespace), like "Foo_bar.jpg"
282 * @return {String}
283 */
284 getMain: function () {
285 return this.getName() + this.getDotExtension();
286 },
287
288 /**
289 * The "text" form, like "Foo bar.jpg"
290 * @return {String}
291 */
292 getMainText: function () {
293 return text( this.getMain() );
294 },
295
296 /**
297 * Get the extension (returns null if there was none)
298 * @return {String|null} extension
299 */
300 getExtension: function () {
301 return this.ext;
302 },
303
304 /**
305 * Convenience method: return string like ".jpg", or "" if no extension
306 * @return {String}
307 */
308 getDotExtension: function () {
309 return this.ext === null ? '' : '.' + this.ext;
310 },
311
312 /**
313 * Return the URL to this title
314 * @return {String}
315 */
316 getUrl: function () {
317 return mw.util.wikiGetlink( this.toString() );
318 },
319
320 /**
321 * Whether this title exists on the wiki.
322 * @return {mixed} Boolean true/false if the information is available. Otherwise null.
323 */
324 exists: function () {
325 return Title.exists( this );
326 }
327 };
328
329 // Alias
330 fn.toString = fn.getPrefixedDb;
331 fn.toText = fn.getPrefixedText;
332
333 // Assign
334 Title.prototype = fn;
335
336 // Expose
337 mw.Title = Title;
338
339 }( mediaWiki, jQuery ) );