1/* 2 * jQuery Autocomplete plugin 1.2.2 3 * 4 * Copyright (c) 2009 Jörn Zaefferer 5 * 6 * Dual licensed under the MIT and GPL licenses: 7 * http://www.opensource.org/licenses/mit-license.php 8 * http://www.gnu.org/licenses/gpl.html 9 * 10 * With small modifications by Alfonso Gómez-Arzola. 11 * See changelog for details. 12 * 13 */ 14 15;(function($) { 16 17$.fn.extend({ 18 cn_autocomplete: function(urlOrData, options) { 19 var isUrl = typeof urlOrData == "string"; 20 options = $.extend({}, $.Autocompleter.defaults, { 21 url: isUrl ? urlOrData : null, 22 data: isUrl ? null : urlOrData, 23 delay: isUrl ? $.Autocompleter.defaults.delay : 10, 24 max: options && !options.scroll ? 10 : 150 25 }, options); 26 27 // if highlight is set to false, replace it with a do-nothing function 28 options.highlight = options.highlight || function(value) { return value; }; 29 30 // if the formatMatch option is not specified, then use formatItem for backwards compatibility 31 options.formatMatch = options.formatMatch || options.formatItem; 32 33 return this.each(function() { 34 new $.Autocompleter(this, options); 35 }); 36 }, 37 result: function(handler) { 38 return this.bind("result", handler); 39 }, 40 search: function(handler) { 41 return this.trigger("search", [handler]); 42 }, 43 flushCache: function() { 44 return this.trigger("flushCache"); 45 }, 46 setOptions: function(options){ 47 return this.trigger("setOptions", [options]); 48 }, 49 unautocomplete: function() { 50 return this.trigger("unautocomplete"); 51 } 52}); 53 54$.Autocompleter = function(input, options) { 55 56 var KEY = { 57 UP: 38, 58 DOWN: 40, 59 DEL: 46, 60 TAB: 9, 61 RETURN: 13, 62 ESC: 27, 63 COMMA: 188, 64 PAGEUP: 33, 65 PAGEDOWN: 34, 66 BACKSPACE: 8 67 }; 68 69 var globalFailure = null; 70 if(options.failure != null && typeof options.failure == "function") { 71 globalFailure = options.failure; 72 } 73 74 // Create $ object for input element 75 var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass); 76 77 var timeout; 78 var previousValue = ""; 79 var cache = $.Autocompleter.Cache(options); 80 var hasFocus = 0; 81 var lastKeyPressCode; 82 var config = { 83 mouseDownOnSelect: false 84 }; 85 var select = $.Autocompleter.Select(options, input, selectCurrent, config); 86 87 var blockSubmit; 88 89 // prevent form submit in opera when selecting with return key 90 $.browser.opera && $(input.form).bind("submit.autocomplete", function() { 91 if (blockSubmit) { 92 blockSubmit = false; 93 return false; 94 } 95 }); 96 97 // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all 98 $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) { 99 // a keypress means the input has focus 100 // avoids issue where input had focus before the autocomplete was applied 101 hasFocus = 1; 102 // track last key pressed 103 lastKeyPressCode = event.keyCode; 104 switch(event.keyCode) { 105 106 case KEY.UP: 107 if ( select.visible() ) { 108 event.preventDefault(); 109 select.prev(); 110 } else { 111 onChange(0, true); 112 } 113 break; 114 115 case KEY.DOWN: 116 if ( select.visible() ) { 117 event.preventDefault(); 118 select.next(); 119 } else { 120 onChange(0, true); 121 } 122 break; 123 124 case KEY.PAGEUP: 125 if ( select.visible() ) { 126 event.preventDefault(); 127 select.pageUp(); 128 } else { 129 onChange(0, true); 130 } 131 break; 132 133 case KEY.PAGEDOWN: 134 if ( select.visible() ) { 135 event.preventDefault(); 136 select.pageDown(); 137 } else { 138 onChange(0, true); 139 } 140 break; 141 142 // matches also semicolon 143 case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA: 144 case KEY.TAB: 145 case KEY.RETURN: 146 if( selectCurrent() ) { 147 // stop default to prevent a form submit, Opera needs special handling 148 event.preventDefault(); 149 blockSubmit = true; 150 return false; 151 } 152 break; 153 154 case KEY.ESC: 155 select.hide(); 156 break; 157 158 default: 159 clearTimeout(timeout); 160 timeout = setTimeout(onChange, options.delay); 161 break; 162 } 163 }).focus(function(){ 164 // track whether the field has focus, we shouldn't process any 165 // results if the field no longer has focus 166 hasFocus++; 167 }).blur(function() { 168 hasFocus = 0; 169 if (!config.mouseDownOnSelect) { 170 hideResults(); 171 } 172 }).click(function() { 173 // show select when clicking in a focused field 174 // but if clickFire is true, don't require field 175 // to be focused to begin with; just show select 176 if( options.clickFire ) { 177 if ( !select.visible() ) { 178 onChange(0, true); 179 } 180 } else { 181 if ( hasFocus++ > 1 && !select.visible() ) { 182 onChange(0, true); 183 } 184 } 185 }).bind("search", function() { 186 // TODO why not just specifying both arguments? 187 var fn = (arguments.length > 1) ? arguments[1] : null; 188 function findValueCallback(q, data) { 189 var result; 190 if( data && data.length ) { 191 for (var i=0; i < data.length; i++) { 192 if( data[i].result.toLowerCase() == q.toLowerCase() ) { 193 result = data[i]; 194 break; 195 } 196 } 197 } 198 if( typeof fn == "function" ) fn(result); 199 else $input.trigger("result", result && [result.data, result.value]); 200 } 201 $.each(trimWords($input.val()), function(i, value) { 202 request(value, findValueCallback, findValueCallback); 203 }); 204 }).bind("flushCache", function() { 205 cache.flush(); 206 }).bind("setOptions", function() { 207 $.extend(true, options, arguments[1]); 208 // if we've updated the data, repopulate 209 if ( "data" in arguments[1] ) 210 cache.populate(); 211 }).bind("unautocomplete", function() { 212 select.unbind(); 213 $input.unbind(); 214 $(input.form).unbind(".cn_autocomplete"); 215 }); 216 217 218 function selectCurrent() { 219 var selected = select.selected(); 220 if( !selected ) 221 return false;
222 223 var v = selected.result; 224 previousValue = v; 225 226 if ( options.multiple ) { 227 var words = trimWords($input.val()); 228 if ( words.length > 1 ) { 229 var seperator = options.multipleSeparator.length; 230 var cursorAt = $(input).selection().start; 231 var wordAt, progress = 0; 232 $.each(words, function(i, word) { 233 progress += word.length; 234 if (cursorAt <= progress) { 235 wordAt = i; 236 return false; 237 } 238 progress += seperator; 239 }); 240 words[wordAt] = v; 241 // TODO this should set the cursor to the right position, but it gets overriden somewhere 242 //$.Autocompleter.Selection(input, progress + seperator, progress + seperator); 243 v = words.join( options.multipleSeparator ); 244 } 245 v += options.multipleSeparator; 246 } 247 248 $input.val(v); 249 hideResultsNow(); 250 $input.trigger("result", [selected.data, selected.value]); 251 return true; 252 } 253 254 function onChange(crap, skipPrevCheck) { 255 if( lastKeyPressCode == KEY.DEL ) { 256 select.hide(); 257 return; 258 } 259 260 var currentValue = $input.val(); 261 262 if ( !skipPrevCheck && currentValue == previousValue ) 263 return; 264 265 previousValue = currentValue; 266 267 currentValue = lastWord(currentValue); 268 if ( currentValue.length >= options.minChars) { 269 $input.addClass(options.loadingClass); 270 if (!options.matchCase) 271 currentValue = currentValue.toLowerCase(); 272 request(currentValue, receiveData, hideResultsNow); 273 } else { 274 stopLoading(); 275 select.hide(); 276 } 277 }; 278 279 function trimWords(value) { 280 if (!value) 281 return [""]; 282 if (!options.multiple) 283 return [$.trim(value)]; 284 return $.map(value.split(options.multipleSeparator), function(word) { 285 return $.trim(value).length ? $.trim(word) : null; 286 }); 287 } 288 289 function lastWord(value) { 290 if ( !options.multiple ) 291 return value; 292 var words = trimWords(value); 293 if (words.length == 1) 294 return words[0]; 295 var cursorAt = $(input).selection().start; 296 if (cursorAt == value.length) { 297 words = trimWords(value) 298 } else { 299 words = trimWords(value.replace(value.substring(cursorAt), "")); 300 } 301 return words[words.length - 1]; 302 } 303 304 // fills in the input box w/the first match (assumed to be the best match) 305 // q: the term entered 306 // sValue: the first matching result 307 function autoFill(q, sValue){ 308 // autofill in the complete box w/the first match as long as the user hasn't entered in more data 309 // if the last user key pressed was backspace, don't autofill 310 if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) { 311 // fill in the value (keep the case the user has typed) 312 $input.val($input.val() + sValue.substring(lastWord(previousValue).length)); 313 // select the portion of the value not typed by the user (so the next character will erase) 314 $(input).selection(previousValue.length, previousValue.length + sValue.length); 315 } 316 }; 317 318 function hideResults() { 319 clearTimeout(timeout); 320 timeout = setTimeout(hideResultsNow, 200); 321 }; 322 323 function hideResultsNow() { 324 var wasVisible = select.visible(); 325 select.hide(); 326 clearTimeout(timeout); 327 stopLoading(); 328 if (options.mustMatch) { 329 // call search and run callback 330 $input.search( 331 function (result){ 332 // if no value found, clear the input box 333 if( !result ) { 334 if (options.multiple) { 335 var words = trimWords($input.val()).slice(0, -1); 336 $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") ); 337 } 338 else { 339 $input.val( "" ); 340 $input.trigger("result", null); 341 } 342 } 343 } 344 ); 345 } 346 }; 347 348 function receiveData(q, data) { 349 if ( data && data.length && hasFocus ) { 350 stopLoading(); 351 select.display(data, q); 352 autoFill(q, data[0].value); 353 select.show(); 354 } else { 355 hideResultsNow(); 356 } 357 }; 358 359 function request(term, success, failure) { 360 if (!options.matchCase) 361 term = term.toLowerCase(); 362 var data = cache.load(term); 363 // recieve the cached data 364 if (data && data.length) { 365 success(term, data); 366 // if an AJAX url has been supplied, try loading the data now 367 } else if( (typeof options.url == "string") && (options.url.length > 0) ){ 368 369 var extraParams = { 370 timestamp: +new Date() 371 }; 372 $.each(options.extraParams, function(key, param) { 373 extraParams[key] = typeof param == "function" ? param() : param; 374 }); 375 376 $.ajax({ 377 // try to leverage ajaxQueue plugin to abort previous requests 378 mode: "abort", 379 // limit abortion to this input 380 port: "autocomplete" + input.name, 381 dataType: options.dataType, 382 url: options.url, 383 data: $.extend({ 384 q: lastWord(term), 385 limit: options.max 386 }, extraParams), 387 success: function(data) { 388 var parsed = options.parse && options.parse(data) || parse(data); 389 cache.add(term, parsed); 390 success(term, parsed); 391 } 392 }); 393 } else { 394 // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match 395 select.emptyList(); 396 if(globalFailure != null) { 397 globalFailure(); 398 } 399 else { 400 failure(term); 401 } 402 } 403 }; 404 405 function parse(data) { 406 var parsed = []; 407 var rows = data.split("\n"); 408 for (var i=0; i < rows.length; i++) { 409 var row = $.trim(rows[i]); 410 if (row) { 411 row = row.split("|"); 412 parsed[parsed.length] = { 413 data: row, 414 value: row[0], 415 result: options.formatResult && options.formatResult(row, row[0]) || row[0] 416 }; 417 } 418 } 419 return parsed; 420 }; 421 422 function stopLoading() { 423 $input.removeClass(options.loadingClass); 424 }; 425 426}; 427 428$.Autocompleter.defaults = { 429 inputClass: "ac_input", 430 resultsClass: "ac_results", 431 loadingClass: "ac_loading", 432 minChars: 1, 433 delay: 400, 434 matchCase: false, 435 matchSubset: true,
436 matchContains: false, 437 cacheLength: 100, 438 max: 1000, 439 mustMatch: false, 440 extraParams: {}, 441 selectFirst: true, 442 formatItem: function(row) { return row[0]; }, 443 formatMatch: null, 444 autoFill: false, 445 width: 0, 446 multiple: false, 447 multipleSeparator: " ", 448 inputFocus: true, 449 clickFire: false, 450 highlight: function(value, term) { 451 return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>"); 452 }, 453 scroll: true, 454 scrollHeight: 180, 455 scrollJumpPosition: true 456}; 457 458$.Autocompleter.Cache = function(options) { 459 460 var data = {}; 461 var length = 0; 462 463 function matchSubset(s, sub) { 464 if (!options.matchCase) 465 s = s.toLowerCase(); 466 var i = s.indexOf(sub); 467 if (options.matchContains == "word"){ 468 i = s.toLowerCase().search("\\b" + sub.toLowerCase()); 469 } 470 if (i == -1) return false; 471 return i == 0 || options.matchContains; 472 }; 473 474 function add(q, value) { 475 if (length > options.cacheLength){ 476 flush(); 477 } 478 if (!data[q]){ 479 length++; 480 } 481 data[q] = value; 482 } 483 484 function populate(){ 485 if( !options.data ) return false; 486 // track the matches 487 var stMatchSets = {}, 488 nullData = 0; 489 490 // no url was specified, we need to adjust the cache length to make sure it fits the local data store 491 if( !options.url ) options.cacheLength = 1; 492 493 // track all options for minChars = 0 494 stMatchSets[""] = []; 495 496 // loop through the array and create a lookup structure 497 for ( var i = 0, ol = options.data.length; i < ol; i++ ) { 498 var rawValue = options.data[i]; 499 // if rawValue is a string, make an array otherwise just reference the array 500 rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue; 501 502 var value = options.formatMatch(rawValue, i+1, options.data.length); 503 if ( typeof(value) === 'undefined' || value === false ) 504 continue; 505 506 var firstChar = value.charAt(0).toLowerCase(); 507 // if no lookup array for this character exists, look it up now 508 if( !stMatchSets[firstChar] ) 509 stMatchSets[firstChar] = []; 510 511 // if the match is a string 512 var row = { 513 value: value, 514 data: rawValue, 515 result: options.formatResult && options.formatResult(rawValue) || value 516 }; 517 518 // push the current match into the set list 519 stMatchSets[firstChar].push(row); 520 521 // keep track of minChars zero items 522 if ( nullData++ < options.max ) { 523 stMatchSets[""].push(row); 524 } 525 }; 526 527 // add the data items to the cache 528 $.each(stMatchSets, function(i, value) { 529 // increase the cache size 530 options.cacheLength++; 531 // add to the cache 532 add(i, value); 533 }); 534 } 535 536 // populate any existing data 537 setTimeout(populate, 25); 538 539 function flush(){ 540 data = {}; 541 length = 0; 542 } 543 544 return { 545 flush: flush, 546 add: add, 547 populate: populate, 548 load: function(q) { 549 if (!options.cacheLength || !length) 550 return null; 551 /* 552 * if dealing w/local data and matchContains than we must make sure 553 * to loop through all the data collections looking for matches 554 */ 555 if( !options.url && options.matchContains ){ 556 // track all matches 557 var csub = []; 558 // loop through all the data grids for matches 559 for( var k in data ){ 560 // don't search through the stMatchSets[""] (minChars: 0) cache 561 // this prevents duplicates 562 if( k.length > 0 ){ 563 var c = data[k]; 564 $.each(c, function(i, x) { 565 // if we've got a match, add it to the array 566 if (matchSubset(x.value, q)) { 567 csub.push(x); 568 } 569 }); 570 } 571 } 572 return csub; 573 } else 574 // if the exact item exists, use it 575 if (data[q]){ 576 return data[q]; 577 } else 578 if (options.matchSubset) { 579 for (var i = q.length - 1; i >= options.minChars; i--) { 580 var c = data[q.substr(0, i)]; 581 if (c) { 582 var csub = []; 583 $.each(c, function(i, x) { 584 if (matchSubset(x.value, q)) { 585 csub[csub.length] = x; 586 } 587 }); 588 return csub; 589 } 590 } 591 } 592 return null; 593 } 594 }; 595}; 596 597$.Autocompleter.Select = function (options, input, select, config) { 598 var CLASSES = { 599 ACTIVE: "ac_over" 600 }; 601 602 var listItems, 603 active = -1, 604 data, 605 term = "", 606 needsInit = true, 607 element, 608 list; 609 610 // Create results
611 function init() { 612 if (!needsInit) 613 return; 614 element = $("<div/>") 615 .hide() 616 .addClass(options.resultsClass) 617 .css("position", "absolute") 618 .appendTo(document.body) 619 .hover(function(event) { 620 // Browsers except FF do not fire mouseup event on scrollbars, resulting in mouseDownOnSelect remaining true, and results list not always hiding. 621 if($(this).is(":visible")) { 622 input.focus(); 623 } 624 config.mouseDownOnSelect = false; 625 }); 626 627 list = $("<ul/>").appendTo(element).mouseover( function(event) { 628 if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') { 629 active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event)); 630 $(target(event)).addClass(CLASSES.ACTIVE); 631 } 632 }).click(function(event) { 633 $(target(event)).addClass(CLASSES.ACTIVE); 634 select(); 635 if( options.inputFocus ) 636 input.focus(); 637 return false; 638 }).mousedown(function() { 639 config.mouseDownOnSelect = true; 640 }).mouseup(function() { 641 config.mouseDownOnSelect = false; 642 }); 643 644 if( options.width > 0 ) 645 element.css("width", options.width); 646 647 needsInit = false; 648 } 649 650 function target(event) { 651 var element = event.target; 652 while(element && element.tagName != "LI") 653 element = element.parentNode; 654 // more fun with IE, sometimes event.target is empty, just ignore it then 655 if(!element) 656 return []; 657 return element; 658 } 659 660 function moveSelect(step) { 661 listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE); 662 movePosition(step); 663 var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE); 664 if(options.scroll) { 665 var offset = 0; 666 listItems.slice(0, active).each(function() { 667 offset += this.offsetHeight; 668 }); 669 if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) { 670 list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight()); 671 } else if(offset < list.scrollTop()) { 672 list.scrollTop(offset); 673 } 674 } 675 }; 676 677 function movePosition(step) { 678 if (options.scrollJumpPosition || (!options.scrollJumpPosition && !((step < 0 && active == 0) || (step > 0 && active == listItems.size() - 1)) )) { 679 active += step; 680 if (active < 0) { 681 active = listItems.size() - 1; 682 } else if (active >= listItems.size()) { 683 active = 0; 684 } 685 } 686 } 687 688 689 function limitNumberOfItems(available) { 690 return options.max && options.max < available 691 ? options.max 692 : available; 693 } 694 695 function fillList() { 696 list.empty(); 697 var max = limitNumberOfItems(data.length); 698 for (var i=0; i < max; i++) { 699 if (!data[i]) 700 continue; 701 var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term); 702 if ( formatted === false ) 703 continue; 704 var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "wps_even" : "wps_odd").appendTo(list)[0]; 705 $.data(li, "ac_data", data[i]); 706 } 707 listItems = list.find("li"); 708 if ( options.selectFirst ) { 709 listItems.slice(0, 1).addClass(CLASSES.ACTIVE); 710 active = 0; 711 } 712 // apply bgiframe if available 713 if ( $.fn.bgiframe ) 714 list.bgiframe(); 715 } 716 717 return { 718 display: function(d, q) { 719 init(); 720 data = d; 721 term = q; 722 fillList(); 723 }, 724 next: function() { 725 moveSelect(1); 726 }, 727 prev: function() { 728 moveSelect(-1); 729 }, 730 pageUp: function() { 731 if (active != 0 && active - 8 < 0) { 732 moveSelect( -active ); 733 } else { 734 moveSelect(-8); 735 } 736 }, 737 pageDown: function() { 738 if (active != listItems.size() - 1 && active + 8 > listItems.size()) { 739 moveSelect( listItems.size() - 1 - active ); 740 } else { 741 moveSelect(8); 742 } 743 }, 744 hide: function() { 745 element && element.hide(); 746 listItems && listItems.removeClass(CLASSES.ACTIVE); 747 active = -1; 748 }, 749 visible : function() { 750 return element && element.is(":visible"); 751 }, 752 current: function() { 753 return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]); 754 }, 755 show: function() { 756 var offset = $(input).offset(); 757 element.css({ 758 width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(), 759 top: offset.top + input.offsetHeight, 760 left: offset.left 761 }).show(); 762 if(options.scroll) { 763 list.scrollTop(0); 764 list.css({ 765 maxHeight: options.scrollHeight, 766 overflow: 'auto' 767 }); 768 769 if($.browser.msie && typeof document.body.style.maxHeight === "undefined") { 770 var listHeight = 0; 771 listItems.each(function() { 772 listHeight += this.offsetHeight; 773 }); 774 var scrollbarsVisible = listHeight > options.scrollHeight; 775 list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight ); 776 if (!scrollbarsVisible) { 777 // IE doesn't recalculate width when scrollbar disappears 778 listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) ); 779 } 780 } 781 782 } 783 }, 784 selected: function() { 785 var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE); 786 return selected && selected.length && $.data(selected[0], "ac_data"); 787 }, 788 emptyList: function (){ 789 list && list.empty(); 790 }, 791 unbind: function() { 792 element && element.remove(); 793 } 794 }; 795}; 796 797$.fn.selection = function(start, end) { 798 if (start !== undefined) { 799 return this.each(function() { 800 if( this.createTextRange ){ 801 var selRange = this.createTextRange(); 802 if (end === undefined || start == end) { 803 selRange.move("character", start); 804 selRange.select(); 805 } else { 806 selRange.collapse(true); 807 selRange.moveStart("character", start); 808 selRange.moveEnd("character", end); 809 selRange.select(); 810 } 811 } else if( this.setSelectionRange ){ 812 this.setSelectionRange(start, end); 813 } else if( this.selectionStart ){ 814 this.selectionStart = start; 815 this.selectionEnd = end; 816 } 817 }); 818 } 819 var field = this[0]; 820 if ( field.createTextRange ) { 821 var range = document.selection.createRange(), 822 orig = field.value, 823 teststring = "<->", 824 textLength = range.text.length; 825 range.text = teststring; 826 var caretAt = field.value.indexOf(teststring); 827 field.value = orig; 828 this.selection(caretAt, caretAt + textLength); 829 return { 830 start: caretAt, 831 end: caretAt + textLength 832 } 833 } else if( field.selectionStart !== undefined ){ 834 return { 835 start: field.selectionStart, 836 end: field.selectionEnd 837 } 838 } 839}; 840 841}
841)(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.