summaryrefslogtreecommitdiffabout
path: root/src
authorMichael Krelin <hacker@klever.net>2008-03-01 17:26:13 (UTC)
committer Michael Krelin <hacker@klever.net>2008-03-01 17:26:13 (UTC)
commitb2e587331d0020fa2bf35e5a5ada249020858e14 (patch) (unidiff)
tree1b6b490275bb54b6f579bad3cf6592a1677aa0a8 /src
parentfe00dd0be8fd88dc8179eed7a38663f07c1288a7 (diff)
downloadfoxri-b2e587331d0020fa2bf35e5a5ada249020858e14.zip
foxri-b2e587331d0020fa2bf35e5a5ada249020858e14.tar.gz
foxri-b2e587331d0020fa2bf35e5a5ada249020858e14.tar.bz2
added handling of append='authority' attribute in URI construction.HEADmaster
Signed-off-by: Michael Krelin <hacker@klever.net>
Diffstat (limited to 'src') (more/less context) (ignore whitespace changes)
-rwxr-xr-xsrc/components/xriProtocolHandler.js2
1 files changed, 2 insertions, 0 deletions
diff --git a/src/components/xriProtocolHandler.js b/src/components/xriProtocolHandler.js
index 3d27784..2e09f64 100755
--- a/src/components/xriProtocolHandler.js
+++ b/src/components/xriProtocolHandler.js
@@ -104,384 +104,386 @@ var nsResolver = {
104 104
105 105
106function runExpr(doc, context, expr, returnType) 106function runExpr(doc, context, expr, returnType)
107{ 107{
108 if (!returnType) 108 if (!returnType)
109 returnType = XP_ANY_TYPE; 109 returnType = XP_ANY_TYPE;
110 var res = doc.evaluate(expr, context, nsResolver, returnType, null); 110 var res = doc.evaluate(expr, context, nsResolver, returnType, null);
111 return res; 111 return res;
112} 112}
113 113
114 114
115function getNumeric(doc, context, expr) 115function getNumeric(doc, context, expr)
116{ 116{
117 var res = runExpr(doc, context, expr, XP_NUMBER_TYPE); 117 var res = runExpr(doc, context, expr, XP_NUMBER_TYPE);
118 if (res) 118 if (res)
119 return res.numberValue; 119 return res.numberValue;
120 return null; 120 return null;
121} 121}
122 122
123 123
124function getString(doc, context, expr) 124function getString(doc, context, expr)
125{ 125{
126 // var res = runExpr(doc, context, expr, XPathResult.STRING_TYPE); 126 // var res = runExpr(doc, context, expr, XPathResult.STRING_TYPE);
127 var res = runExpr(doc, context, expr, XP_STRING_TYPE); 127 var res = runExpr(doc, context, expr, XP_STRING_TYPE);
128 if (res) 128 if (res)
129 return res.stringValue; 129 return res.stringValue;
130 return null; 130 return null;
131} 131}
132 132
133function getNode(doc, context, expr) 133function getNode(doc, context, expr)
134{ 134{
135 var res = runExpr(doc, context, expr, XP_FIRST_ORDERED_NODE_TYPE); 135 var res = runExpr(doc, context, expr, XP_FIRST_ORDERED_NODE_TYPE);
136 if (res) 136 if (res)
137 return res.singleNodeValue; 137 return res.singleNodeValue;
138 return null; 138 return null;
139} 139}
140 140
141 141
142function getFinalXRD(doc) 142function getFinalXRD(doc)
143{ 143{
144 var lastNode = doc.firstChild; 144 var lastNode = doc.firstChild;
145 while (true) { 145 while (true) {
146 var node = getNode(doc, lastNode, "xrds:XRDS[position()=last()]"); 146 var node = getNode(doc, lastNode, "xrds:XRDS[position()=last()]");
147 if (!node) 147 if (!node)
148 break; 148 break;
149 lastNode = node; 149 lastNode = node;
150 } 150 }
151 151
152 return getNode(doc, lastNode, "xrd:XRD[position()=last()]"); 152 return getNode(doc, lastNode, "xrd:XRD[position()=last()]");
153} 153}
154 154
155 155
156function isIName(xri) 156function isIName(xri)
157{ 157{
158 if (xri.match('^xri://.!', 'i')) { 158 if (xri.match('^xri://.!', 'i')) {
159 return false; 159 return false;
160 } 160 }
161 if (xri.match('^.!', 'i')) { 161 if (xri.match('^.!', 'i')) {
162 return false; 162 return false;
163 } 163 }
164 return true; 164 return true;
165} 165}
166 166
167 167
168function arraySearch(a, re) 168function arraySearch(a, re)
169{ 169{
170 var returnArr = new Array(); 170 var returnArr = new Array();
171 var i; 171 var i;
172 for (i = 0; i < a.length; i++) { 172 for (i = 0; i < a.length; i++) {
173 if (a[i].match(re)) { 173 if (a[i].match(re)) {
174 returnArr.push(a[i]); 174 returnArr.push(a[i]);
175 } 175 }
176 } 176 }
177 177
178 return returnArr; 178 return returnArr;
179} 179}
180 180
181 181
182function renderService(srv, doc, qxri) 182function renderService(srv, doc, qxri)
183{ 183{
184 var html_types = ''; 184 var html_types = '';
185 var html_paths = ''; 185 var html_paths = '';
186 var html_mediatypes = ''; 186 var html_mediatypes = '';
187 var html_uris = ''; 187 var html_uris = '';
188 var html_actions = ''; 188 var html_actions = '';
189 189
190 var serviceName = friendlyServiceName(null); 190 var serviceName = friendlyServiceName(null);
191 var serviceType; // the last non-null Type 191 var serviceType; // the last non-null Type
192 var knownServiceType; // first recognized service type 192 var knownServiceType; // first recognized service type
193 193
194 // get the types 194 // get the types
195 var res = runExpr(doc, srv, "xrd:Type/text()"); 195 var res = runExpr(doc, srv, "xrd:Type/text()");
196 var t; 196 var t;
197 while (t = res.iterateNext()) { 197 while (t = res.iterateNext()) {
198 if (t.nodeValue) { 198 if (t.nodeValue) {
199 if (!knownServiceType && isKnownServiceType(t.nodeValue)) { 199 if (!knownServiceType && isKnownServiceType(t.nodeValue)) {
200 knownServiceType = t.nodeValue; 200 knownServiceType = t.nodeValue;
201 } 201 }
202 202
203 serviceType = t.nodeValue; 203 serviceType = t.nodeValue;
204 html_types += "<strong>Type:</strong> " + t.nodeValue + "<br/>"; 204 html_types += "<strong>Type:</strong> " + t.nodeValue + "<br/>";
205 } 205 }
206 } 206 }
207 207
208 // get the paths 208 // get the paths
209 res = runExpr(doc, srv, "xrd:Path/text()"); 209 res = runExpr(doc, srv, "xrd:Path/text()");
210 var p; 210 var p;
211 var qxri_prefix = qxri; 211 var qxri_prefix = qxri;
212 if (qxri_prefix.charAt(qxri_prefix.length - 1) != '/') { 212 if (qxri_prefix.charAt(qxri_prefix.length - 1) != '/') {
213 qxri_prefix += '/'; 213 qxri_prefix += '/';
214 } 214 }
215 215
216 while (p = res.iterateNext()) { 216 while (p = res.iterateNext()) {
217 if (p.nodeValue) { 217 if (p.nodeValue) {
218 html_paths += "<strong>Path:</strong> " + p.nodeValue 218 html_paths += "<strong>Path:</strong> " + p.nodeValue
219 + " [ <tt><a href=\"" + qxri_prefix + p.nodeValue + "\">" 219 + " [ <tt><a href=\"" + qxri_prefix + p.nodeValue + "\">"
220 + qxri_prefix + p.nodeValue + "</a></tt> ]" 220 + qxri_prefix + p.nodeValue + "</a></tt> ]"
221 + "<br/>\n"; 221 + "<br/>\n";
222 } 222 }
223 } 223 }
224 224
225 225
226 // get the mediatypes 226 // get the mediatypes
227 mediaTypes = new Array(); 227 mediaTypes = new Array();
228 res = runExpr(doc, srv, "xrd:MediaType/text()"); 228 res = runExpr(doc, srv, "xrd:MediaType/text()");
229 var m; 229 var m;
230 while (m = res.iterateNext()) { 230 while (m = res.iterateNext()) {
231 if (!knownServiceType) { 231 if (!knownServiceType) {
232 var srvType = guessServiceTypeByMime(m.nodeValue); 232 var srvType = guessServiceTypeByMime(m.nodeValue);
233 knownServiceType = srvType? srvType : null; 233 knownServiceType = srvType? srvType : null;
234 } 234 }
235 235
236 mediaTypes.push(m.nodeValue); 236 mediaTypes.push(m.nodeValue);
237 if (m.nodeValue) { 237 if (m.nodeValue) {
238 html_mediatypes += "<strong>Media Type:</strong> " + m.nodeValue + "<br/>"; 238 html_mediatypes += "<strong>Media Type:</strong> " + m.nodeValue + "<br/>";
239 } 239 }
240 } 240 }
241 241
242 242
243 res = runExpr(doc, srv, "xrd:URI"); 243 res = runExpr(doc, srv, "xrd:URI");
244 var uu; 244 var uu;
245 while (uu = res.iterateNext()) { 245 while (uu = res.iterateNext()) {
246 var u = uu.firstChild; 246 var u = uu.firstChild;
247 if (!(u.nodeValue && u.nodeType==3)) 247 if (!(u.nodeValue && u.nodeType==3))
248 continue; 248 continue;
249 249
250 var srvType = guessServiceTypeByURI(u.nodeValue); 250 var srvType = guessServiceTypeByURI(u.nodeValue);
251 if (!knownServiceType) { 251 if (!knownServiceType) {
252 knownServiceType = srvType; 252 knownServiceType = srvType;
253 } 253 }
254 254
255 html_uris += "<div class=\"" + getServiceClass(srvType) + "\">"; 255 html_uris += "<div class=\"" + getServiceClass(srvType) + "\">";
256 256
257 var linkContent = u.nodeValue; 257 var linkContent = u.nodeValue;
258 var uriParts = u.nodeValue.match('^(.*):(.*)$'); 258 var uriParts = u.nodeValue.match('^(.*):(.*)$');
259 if (!uriParts) 259 if (!uriParts)
260 continue; 260 continue;
261 261
262 if (uriParts[1] == 'data') { 262 if (uriParts[1] == 'data') {
263 uriParts = uriParts[2].match('^(.*/.*),(.*)'); 263 uriParts = uriParts[2].match('^(.*/.*),(.*)');
264 if (uriParts && uriParts[1].match('^image/', 'i')) { 264 if (uriParts && uriParts[1].match('^image/', 'i')) {
265 linkContent = "<img src=\"" + u.nodeValue + "\"/>"; 265 linkContent = "<img src=\"" + u.nodeValue + "\"/>";
266 } 266 }
267 else if (uriParts) { 267 else if (uriParts) {
268 linkContent = uriParts[1] + " data"; 268 linkContent = uriParts[1] + " data";
269 } 269 }
270 } 270 }
271 else if (uriParts[1] == 'skype') { 271 else if (uriParts[1] == 'skype') {
272 uriParts = uriParts[2].match('^(.*)\\?(.*)'); 272 uriParts = uriParts[2].match('^(.*)\\?(.*)');
273 if (uriParts) { 273 if (uriParts) {
274 if (uriParts[2] == "call") { 274 if (uriParts[2] == "call") {
275 linkContent = "<img src=\"chrome://foxri/content/skype_call_large.png\" alt=\"Call " + uriParts[1] + "\"/>"; 275 linkContent = "<img src=\"chrome://foxri/content/skype_call_large.png\" alt=\"Call " + uriParts[1] + "\"/>";
276 } 276 }
277 else if (uriParts[2] == "chat") { 277 else if (uriParts[2] == "chat") {
278 linkContent = "<img src=\"chrome://foxri/content/skype_chat_large.png\" alt=\"Chat with " + uriParts[1] + "\"/>"; 278 linkContent = "<img src=\"chrome://foxri/content/skype_chat_large.png\" alt=\"Chat with " + uriParts[1] + "\"/>";
279 } 279 }
280 else if (uriParts[2] == "add") { 280 else if (uriParts[2] == "add") {
281 linkContent = "<img src=\"chrome://foxri/content/skype_add_large.png\" alt=\"Add " + uriParts[1] + " to Skype\"/>"; 281 linkContent = "<img src=\"chrome://foxri/content/skype_add_large.png\" alt=\"Add " + uriParts[1] + " to Skype\"/>";
282 } 282 }
283 } 283 }
284 } 284 }
285 else if (uriParts[1] == 'aim') { 285 else if (uriParts[1] == 'aim') {
286 uriParts = uriParts[2].match('^(.*)\\?.*screenname=([^&]*)', 'i'); 286 uriParts = uriParts[2].match('^(.*)\\?.*screenname=([^&]*)', 'i');
287 if (uriParts) { 287 if (uriParts) {
288 linkContent = "<img src=\"chrome://foxri/content/aim_logo.gif\" alt=\"Chat with " + uriParts[2] + "\"/> Chat with " + uriParts[2]; 288 linkContent = "<img src=\"chrome://foxri/content/aim_logo.gif\" alt=\"Chat with " + uriParts[2] + "\"/> Chat with " + uriParts[2];
289 } 289 }
290 } 290 }
291 291
292 var linkhref = u.nodeValue; 292 var linkhref = u.nodeValue;
293 var xrap = uu.getAttribute('append'); 293 var xrap = uu.getAttribute('append');
294 if(xrap=='qxri') { 294 if(xrap=='qxri') {
295 linkhref += qxri.replace(/^xri:\/\//,''); 295 linkhref += qxri.replace(/^xri:\/\//,'');
296 }else if(xrap=='authority') {
297 linkhref += qxri.replace(/^xri:\/\//,'').replace(/\//.*,'');
296 }else if(xrap!=null){ 298 }else if(xrap!=null){
297 dump("Unhandled @append: "+xrap+"\n"); 299 dump("Unhandled @append: "+xrap+"\n");
298 } 300 }
299 html_uris += "<a href=\""+linkhref+"\">" 301 html_uris += "<a href=\""+linkhref+"\">"
300 + linkContent + "</a>"; 302 + linkContent + "</a>";
301 html_uris += "</div>"; 303 html_uris += "</div>";
302 } 304 }
303 305
304 var html = "<div class=\"service srv_" + getServiceClass(knownServiceType) + "\">\n"; 306 var html = "<div class=\"service srv_" + getServiceClass(knownServiceType) + "\">\n";
305 html += html_types; 307 html += html_types;
306 html += html_paths; 308 html += html_paths;
307 html += html_mediatypes; 309 html += html_mediatypes;
308 if (html_uris) { 310 if (html_uris) {
309 html += "<strong>URI(s):</strong><br/>\n"; 311 html += "<strong>URI(s):</strong><br/>\n";
310 html += html_uris; 312 html += html_uris;
311 } 313 }
312 html += "</div>"; 314 html += "</div>";
313 315
314 return html; 316 return html;
315} 317}
316 318
317 319
318 320
319function isKnownServiceType(type) 321function isKnownServiceType(type)
320{ 322{
321 if (type.toLowerCase() in SERVICE_CLASSES) { 323 if (type.toLowerCase() in SERVICE_CLASSES) {
322 return true; 324 return true;
323 } 325 }
324 return false; 326 return false;
325} 327}
326 328
327function getServiceClass(type) 329function getServiceClass(type)
328{ 330{
329 if (type && isKnownServiceType(type)) { 331 if (type && isKnownServiceType(type)) {
330 return SERVICE_CLASSES[type.toLowerCase()]; 332 return SERVICE_CLASSES[type.toLowerCase()];
331 } 333 }
332 return type; 334 return type;
333} 335}
334 336
335 337
336function guessServiceTypeByURI(uri) 338function guessServiceTypeByURI(uri)
337{ 339{
338 if (uri == null || uri == "") { 340 if (uri == null || uri == "") {
339 return "unknown"; 341 return "unknown";
340 } 342 }
341 if (uri.match(/^https?:/i)) { 343 if (uri.match(/^https?:/i)) {
342 return "www"; 344 return "www";
343 } 345 }
344 else if (uri.match(/^skype:/i)) { 346 else if (uri.match(/^skype:/i)) {
345 return "skype"; 347 return "skype";
346 } 348 }
347 else if (uri.match(/^aim:/i)) { 349 else if (uri.match(/^aim:/i)) {
348 return "aim"; 350 return "aim";
349 } 351 }
350 else if (uri.match(/^xmpp:/i)) { 352 else if (uri.match(/^xmpp:/i)) {
351 return "jabber"; 353 return "jabber";
352 } 354 }
353 else if (uri.match(/^tel:/i)) { 355 else if (uri.match(/^tel:/i)) {
354 return "tel"; 356 return "tel";
355 } 357 }
356 else if (uri.match(/^callto:/i)) { 358 else if (uri.match(/^callto:/i)) {
357 return "callto"; 359 return "callto";
358 } 360 }
359 else if (uri.match(/^telnet:/i)) { 361 else if (uri.match(/^telnet:/i)) {
360 return "telnet"; 362 return "telnet";
361 } 363 }
362 else if (uri.match(/^news:/i)) { 364 else if (uri.match(/^news:/i)) {
363 return "news"; 365 return "news";
364 } 366 }
365 else if (uri.match(/^nntp:/i)) { 367 else if (uri.match(/^nntp:/i)) {
366 return "nntp"; 368 return "nntp";
367 } 369 }
368 else if (uri.match(/^ftp:/i)) { 370 else if (uri.match(/^ftp:/i)) {
369 return "ftp"; 371 return "ftp";
370 } 372 }
371 else if (uri.match(/^mailto:/i)) { 373 else if (uri.match(/^mailto:/i)) {
372 return "email"; 374 return "email";
373 } 375 }
374 else if (uri.match(/^urn:/i)) { 376 else if (uri.match(/^urn:/i)) {
375 return "urn"; 377 return "urn";
376 } 378 }
377 else if (uri.match(/^data:/i)) { 379 else if (uri.match(/^data:/i)) {
378 return "data"; 380 return "data";
379 } 381 }
380 else if (uri.match(/^feed:/i)) { 382 else if (uri.match(/^feed:/i)) {
381 return "feed"; 383 return "feed";
382 } 384 }
383 return "unknown"; 385 return "unknown";
384} 386}
385 387
386 388
387function guessServiceTypeByMime(mimeType) 389function guessServiceTypeByMime(mimeType)
388{ 390{
389 if (mimeType.match(/^application\/(rss|atom)\+xml/i)) { 391 if (mimeType.match(/^application\/(rss|atom)\+xml/i)) {
390 dump("feed detected!\n"); 392 dump("feed detected!\n");
391 return "feed"; 393 return "feed";
392 } 394 }
393 else if (mimeType.match(/^image\//i)) { 395 else if (mimeType.match(/^image\//i)) {
394 return "image"; 396 return "image";
395 } 397 }
396 return null; 398 return null;
397} 399}
398 400
399 401
400 402
401function friendlyServiceName(srvType, uri) 403function friendlyServiceName(srvType, uri)
402{ 404{
403 if (srvType && srvType == "xri://+i-service*(+contact)*($v*1.0)") { 405 if (srvType && srvType == "xri://+i-service*(+contact)*($v*1.0)") {
404 return "Contact Service"; 406 return "Contact Service";
405 } 407 }
406 else if (srvType && ( 408 else if (srvType && (
407 srvType == "http://openid.net/signon/1.0" 409 srvType == "http://openid.net/signon/1.0"
408 || srvType == "http://openid.net/signon/1.1" 410 || srvType == "http://openid.net/signon/1.1"
409 || srvType == "http://specs.openid.net/auth/2.0/signon" 411 || srvType == "http://specs.openid.net/auth/2.0/signon"
410 || srcType == "http://specs.openid.net/auth/2.0/server" 412 || srcType == "http://specs.openid.net/auth/2.0/server"
411 ) ) { 413 ) ) {
412 return "OpenID Authentication Service"; 414 return "OpenID Authentication Service";
413 } 415 }
414 else if (srvType && srvType == "xri://$res*auth*($v*2.0)") { 416 else if (srvType && srvType == "xri://$res*auth*($v*2.0)") {
415 return "Authority Resolution Service"; 417 return "Authority Resolution Service";
416 } 418 }
417 else { 419 else {
418 if (uri == null) { 420 if (uri == null) {
419 return "Generic Service"; 421 return "Generic Service";
420 } 422 }
421 if (uri.match(/^https?:/i)) { 423 if (uri.match(/^https?:/i)) {
422 return "Web Link"; 424 return "Web Link";
423 } 425 }
424 else if (uri.match(/^skype:/i)) { 426 else if (uri.match(/^skype:/i)) {
425 var user = uri.substring("skype:".length, uri.indexOf('?')); 427 var user = uri.substring("skype:".length, uri.indexOf('?'));
426 return "Skype <a href=\"" + uri + "\"><img src=\"chrome://foxri/content/skype_call.png\"></a>"; 428 return "Skype <a href=\"" + uri + "\"><img src=\"chrome://foxri/content/skype_call.png\"></a>";
427 } 429 }
428 else if (uri.match(/^mailto:/i)) { 430 else if (uri.match(/^mailto:/i)) {
429 var qmark = uri.indexOf('?'); 431 var qmark = uri.indexOf('?');
430 var email = (qmark == -1)? 432 var email = (qmark == -1)?
431 uri.substr("mailto:".length) : 433 uri.substr("mailto:".length) :
432 uri.substring("mailto:".length, qmark); 434 uri.substring("mailto:".length, qmark);
433 return "Email (address: " + email + ")"; 435 return "Email (address: " + email + ")";
434 } 436 }
435 else if (srvType != null) { 437 else if (srvType != null) {
436 return srvType; // return verbatim 438 return srvType; // return verbatim
437 } 439 }
438 return "Generic Service"; 440 return "Generic Service";
439 } 441 }
440} 442}
441 443
442 444
443 445
444 446
445function subHTML(template, vars) 447function subHTML(template, vars)
446{ 448{
447 for (key in vars) { 449 for (key in vars) {
448 template = template.replace(key, vars[key], 'g'); 450 template = template.replace(key, vars[key], 'g');
449 } 451 }
450 return template; 452 return template;
451} 453}
452 454
453 455
454/// Given the completed XMLHttpRequest object, renders the XRDS 456/// Given the completed XMLHttpRequest object, renders the XRDS
455function renderXRDS(xmlDoc) 457function renderXRDS(xmlDoc)
456{ 458{
457 var x = xmlDoc; 459 var x = xmlDoc;
458 var qxri = getString(x, x, "/xrds:XRDS/@ref"); 460 var qxri = getString(x, x, "/xrds:XRDS/@ref");
459 461
460 var html = subHTML(HTML_HEAD, { '#QXRI#': qxri }); 462 var html = subHTML(HTML_HEAD, { '#QXRI#': qxri });
461 463
462 // TODO: render parents as well 464 // TODO: render parents as well
463 465
464 var lastNode = getFinalXRD(x); 466 var lastNode = getFinalXRD(x);
465 if (lastNode) { 467 if (lastNode) {
466 var stat = getString(x, lastNode, "xrd:Status/@code"); 468 var stat = getString(x, lastNode, "xrd:Status/@code");
467 if (stat == "100") { 469 if (stat == "100") {
468 html += "<h3>Exploring <strong>" + qxri + "</strong></h3>"; 470 html += "<h3>Exploring <strong>" + qxri + "</strong></h3>";
469 } 471 }
470 else { 472 else {
471 var msg = getString(x, lastNode, "xrd:Status/text()"); 473 var msg = getString(x, lastNode, "xrd:Status/text()");
472 html += "<h3 class=\"error\"><strong>" + qxri + "</strong> failed to resolve (reason: " + stat + " - " + msg + ")</h3>"; 474 html += "<h3 class=\"error\"><strong>" + qxri + "</strong> failed to resolve (reason: " + stat + " - " + msg + ")</h3>";
473 } 475 }
474 476
475 html += "<br/>"; 477 html += "<br/>";
476 478
477 var services = runExpr(x, lastNode, "xrd:Service"); 479 var services = runExpr(x, lastNode, "xrd:Service");
478 var s; 480 var s;
479 var count = getNumeric(x, lastNode, "count(xrd:Service)"); 481 var count = getNumeric(x, lastNode, "count(xrd:Service)");
480 if (count > 0) { 482 if (count > 0) {
481 while (s = services.iterateNext()) { 483 while (s = services.iterateNext()) {
482 count++; 484 count++;
483 html += renderService(s, x, qxri); 485 html += renderService(s, x, qxri);
484 } 486 }
485 } 487 }
486 else if (stat == '222') { 488 else if (stat == '222') {
487 var xriType = isIName(qxri)? 'I-name' : 'I-number'; 489 var xriType = isIName(qxri)? 'I-name' : 'I-number';