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