1/** 2 * @license AngularJS v1.3.20 3 * (c) 2010-2014 Google, Inc. http://angularjs.org 4 * License: MIT 5 */ 6(function(window, angular, undefined) {'use strict'; 7 8var $resourceMinErr = angular.$$minErr('$resource'); 9 10// Helper functions and regex to lookup a dotted path on an object 11// stopping at undefined/null. The path must be composed of ASCII 12// identifiers (just like $parse) 13var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/; 14 15function isValidDottedPath(path) { 16 return (path != null && path !== '' && path !== 'hasOwnProperty' && 17 MEMBER_NAME_REGEX.test('.' + path)); 18} 19 20function lookupDottedPath(obj, path) { 21 if (!isValidDottedPath(path)) { 22 throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path); 23 } 24 var keys = path.split('.'); 25 for (var i = 0, ii = keys.length; i < ii && obj !== undefined; i++) { 26 var key = keys[i]; 27 obj = (obj !== null) ? obj[key] : undefined; 28 } 29 return obj; 30} 31 32/** 33 * Create a shallow copy of an object and clear other fields from the destination 34 */ 35function shallowClearAndCopy(src, dst) { 36 dst = dst || {}; 37 38 angular.forEach(dst, function(value, key) { 39 delete dst[key]; 40 }); 41 42 for (var key in src) { 43 if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { 44 dst[key] = src[key]; 45 } 46 } 47 48 return dst; 49} 50 51/** 52 * @ngdoc module 53 * @name ngResource 54 * @description 55 * 56 * # ngResource 57 * 58 * The `ngResource` module provides interaction support with RESTful services 59 * via the $resource service. 60 * 61 * 62 * <div doc-module-components="ngResource"></div> 63 * 64 * See {@link ngResource.$resource `$resource`} for usage. 65 */ 66 67/** 68 * @ngdoc service 69 * @name $resource 70 * @requires $http 71 * 72 * @description 73 * A factory which creates a resource object that lets you interact with 74 * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. 75 * 76 * The returned resource object has action methods which provide high-level behaviors without 77 * the need to interact with the low level {@link ng.$http $http} service. 78 * 79 * Requires the {@link ngResource `ngResource`} module to be installed. 80 * 81 * By default, trailing slashes will be stripped from the calculated URLs, 82 * which can pose problems with server backends that do not expect that 83 * behavior. This can be disabled by configuring the `$resourceProvider` like 84 * this: 85 * 86 * ```js 87 app.config(['$resourceProvider', function($resourceProvider) { 88 // Don't strip trailing slashes from calculated URLs 89 $resourceProvider.defaults.stripTrailingSlashes = false; 90 }]); 91 * ``` 92 * 93 * @param {string} url A parametrized URL template with parameters prefixed by `:` as in 94 * `/user/:username`. If you are using a URL with a port number (e.g. 95 * `http://example.com:8080/api`), it will be respected. 96 * 97 * If you are using a url with a suffix, just add the suffix, like this: 98 * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')` 99 * or even `$resource('http://example.com/resource/:resource_id.:format')` 100 * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be 101 * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you 102 * can escape it with `/\.`. 103 * 104 * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in 105 * `actions` methods. If any of the parameter value is a function, it will be executed every time 106 * when a param value needs to be obtained for a request (unless the param was overridden). 107 * 108 * Each key value in the parameter object is first bound to url template if present and then any 109 * excess keys are appended to the url search query after the `?`. 110 * 111 * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in 112 * URL `/path/greet?salutation=Hello`. 113 * 114 * If the parameter value is prefixed with `@` then the value for that parameter will be extracted 115 * from the corresponding property on the `data` object (provided when calling an action method). For
116 * example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of `someParam` 117 * will be `data.someProp`. 118 * 119 * @param {Object.<Object>=} actions Hash with declaration of custom actions that should extend 120 * the default set of resource actions. The declaration should be created in the format of {@link 121 * ng.$http#usage $http.config}: 122 * 123 * {action1: {method:?, params:?, isArray:?, headers:?, ...}, 124 * action2: {method:?, params:?, isArray:?, headers:?, ...}, 125 * ...} 126 * 127 * Where: 128 * 129 * - **`action`** â {string} â The name of action. This name becomes the name of the method on 130 * your resource object. 131 * - **`method`** â {string} â Case insensitive HTTP method (e.g. `GET`, `POST`, `PUT`, 132 * `DELETE`, `JSONP`, etc). 133 * - **`params`** â {Object=} â Optional set of pre-bound parameters for this action. If any of 134 * the parameter value is a function, it will be executed every time when a param value needs to 135 * be obtained for a request (unless the param was overridden). 136 * - **`url`** â {string} â action specific `url` override. The url templating is supported just 137 * like for the resource-level urls. 138 * - **`isArray`** â {boolean=} â If true then the returned object for this action is an array, 139 * see `returns` section. 140 * - **`transformRequest`** â 141 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` â 142 * transform function or an array of such functions. The transform function takes the http 143 * request body and headers and returns its transformed (typically serialized) version. 144 * By default, transformRequest will contain one function that checks if the request data is 145 * an object and serializes to using `angular.toJson`. To prevent this behavior, set 146 * `transformRequest` to an empty array: `transformRequest: []` 147 * - **`transformResponse`** â 148 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` â 149 * transform function or an array of such functions. The transform function takes the http 150 * response body and headers and returns its transformed (typically deserialized) version. 151 * By default, transformResponse will contain one function that checks if the response looks like 152 * a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior, set 153 * `transformResponse` to an empty array: `transformResponse: []` 154 * - **`cache`** â `{boolean|Cache}` â If true, a default $http cache will be used to cache the 155 * GET request, otherwise if a cache instance built with 156 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for 157 * caching. 158 * - **`timeout`** â `{number|Promise}` â timeout in milliseconds, or {@link ng.$q promise} that 159 * should abort the request when resolved. 160 * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the 161 * XHR object. See 162 * [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5) 163 * for more information. 164 * - **`responseType`** - `{string}` - see 165 * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). 166 * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods - 167 * `response` and `responseError`. Both `response` and `responseError` interceptors get called 168 * with `http response` object. See {@link ng.$http $http interceptors}. 169 * 170 * @param {Object} options Hash with custom settings that should extend the 171 * default `$resourceProvider` behavior. The only supported option is 172 * 173 * Where: 174 * 175 * - **`stripTrailingSlashes`** â {boolean} â If true then the trailing 176 * slashes from any calculated URL will be stripped. (Defaults to true.) 177 * 178 * @returns {Object} A resource "class" object with methods for the default set of resource actions 179 * optionally extended with custom `actions`. The default set contains these actions: 180 * ```js 181 * { 'get': {method:'GET'}, 182 * 'save': {method:'POST'}, 183 * 'query': {method:'GET', isArray:true}, 184 * 'remove': {method:'DELETE'}, 185 * 'delete': {method:'DELETE'} }; 186 * ``` 187 * 188 * Calling these methods invoke an {@link ng.$http} with the specified http method, 189 * destination and parameters. When the data is returned from the server then the object is an 190 * instance of the resource class. The actions `save`, `remove` and `delete` are available on it 191 * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, 192 * read, update, delete) on server-side data like this: 193 * ```js 194 * var User = $resource('/user/:userId', {userId:'@id'}); 195 * var user = User.get({userId:123}, function() { 196 * user.abc = true; 197 * user.$save(); 198 * }); 199 * ``` 200 * 201 * It is important to realize that invoking a $resource object method immediately returns an 202 * empty reference (object or array depending on `isArray`). Once the data is returned from the 203 * server the existing reference is populated with the actual data. This is a useful trick since 204 * usually the resource is assigned to a model which is then rendered by the view. Having an empty 205 * object results in no rendering, once the data arrives from the server then the object is 206 * populated with the data and the view automatically re-renders itself showing the new data. This 207 * means that in most cases one never has to write a callback function for the action methods. 208 * 209 * The action methods on the class object or instance object can be invoked with the following 210 * parameters: 211 * 212 * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` 213 * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` 214 * - non-GET instance actions: `instance.$action([parameters], [success], [error])` 215 * 216 * 217 * Success callback is called with (value, responseHeaders) arguments. Error callback is called 218 * with (httpResponse) argument. 219 * 220 * Class actions return empty instance (with additional properties below). 221 * Instance actions return promise of the action. 222 * 223 * The Resource instances and collection have these additional properties: 224 * 225 * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this 226 * instance or collection. 227 * 228 * On success, the promise is resolved with the same resource instance or collection object, 229 * updated with data from server. This makes it easy to use in 230 * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view 231 * rendering until the resource(s) are loaded. 232 * 233 * On failure, the promise is resolved with the {@link ng.$http http response} object, without 234 * the `resource` property. 235 * 236 * If an interceptor object was provided, the promise will instead be resolved with the value 237 * returned by the interceptor. 238 * 239 * - `$resolved`: `true` after first server interaction is completed (either with success or 240 * rejection), `false` before that. Knowing if the Resource has been resolved
240is useful in 241 * data-binding. 242 * 243 * @example 244 * 245 * # Credit card resource 246 * 247 * ```js 248 // Define CreditCard class 249 var CreditCard = $resource('/user/:userId/card/:cardId', 250 {userId:123, cardId:'@id'}, { 251 charge: {method:'POST', params:{charge:true}} 252 }); 253 254 // We can retrieve a collection from the server 255 var cards = CreditCard.query(function() { 256 // GET: /user/123/card 257 // server returns: [ {id:456, number:'1234', name:'Smith'} ]; 258 259 var card = cards[0]; 260 // each item is an instance of CreditCard 261 expect(card instanceof CreditCard).toEqual(true); 262 card.name = "J. Smith"; 263 // non GET methods are mapped onto the instances 264 card.$save(); 265 // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'} 266 // server returns: {id:456, number:'1234', name: 'J. Smith'}; 267 268 // our custom method is mapped as well. 269 card.$charge({amount:9.99}); 270 // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'} 271 }); 272 273 // we can create an instance as well 274 var newCard = new CreditCard({number:'0123'}); 275 newCard.name = "Mike Smith"; 276 newCard.$save(); 277 // POST: /user/123/card {number:'0123', name:'Mike Smith'} 278 // server returns: {id:789, number:'0123', name: 'Mike Smith'}; 279 expect(newCard.id).toEqual(789); 280 * ``` 281 * 282 * The object returned from this function execution is a resource "class" which has "static" method 283 * for each action in the definition. 284 * 285 * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and 286 * `headers`. 287 * When the data is returned from the server then the object is an instance of the resource type and 288 * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD 289 * operations (create, read, update, delete) on server-side data. 290 291 ```js 292 var User = $resource('/user/:userId', {userId:'@id'}); 293 User.get({userId:123}, function(user) { 294 user.abc = true; 295 user.$save(); 296 }); 297 ``` 298 * 299 * It's worth noting that the success callback for `get`, `query` and other methods gets passed 300 * in the response that came from the server as well as $http header getter function, so one 301 * could rewrite the above example and get access to http headers as: 302 * 303 ```js 304 var User = $resource('/user/:userId', {userId:'@id'}); 305 User.get({userId:123}, function(u, getResponseHeaders){ 306 u.abc = true; 307 u.$save(function(u, putResponseHeaders) { 308 //u => saved user object 309 //putResponseHeaders => $http header getter 310 }); 311 }); 312 ``` 313 * 314 * You can also access the raw `$http` promise via the `$promise` property on the object returned 315 * 316 ``` 317 var User = $resource('/user/:userId', {userId:'@id'}); 318 User.get({userId:123}) 319 .$promise.then(function(user) { 320 $scope.user = user; 321 }); 322 ``` 323 324 * # Creating a custom 'PUT' request 325 * In this example we create a custom method on our resource to make a PUT request 326 * ```js 327 * var app = angular.module('app', ['ngResource', 'ngRoute']); 328 * 329 * // Some APIs expect a PUT request in the format URL/object/ID 330 * // Here we are creating an 'update' method 331 * app.factory('Notes', ['$resource', function($resource) { 332 * return $resource('/notes/:id', null, 333 * { 334 * 'update': { method:'PUT' } 335 * }); 336 * }]); 337 * 338 * // In our controller we get the ID from the URL using ngRoute and $routeParams 339 * // We pass in $routeParams and our Notes factory along with $scope 340 * app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes', 341 function($scope, $routeParams, Notes) { 342 * // First get a note object from the factory 343 * var note = Notes.get({ id:$routeParams.id }); 344 * $id = note.id; 345 * 346 * // Now call update passing in the ID first then the object you are updating 347 * Notes.update({ id:$id }, note); 348 * 349 * // This will PUT /notes/ID with the note object in the request payload 350 * }]); 351 * ``` 352 */ 353angular.module('ngResource', ['ng']). 354 provider('$resource', function() { 355 var provider = this; 356 357 this.defaults = { 358 // Strip slashes by default
359 stripTrailingSlashes: true, 360 361 // Default actions configuration 362 actions: { 363 'get': {method: 'GET'}, 364 'save': {method: 'POST'}, 365 'query': {method: 'GET', isArray: true}, 366 'remove': {method: 'DELETE'}, 367 'delete': {method: 'DELETE'} 368 } 369 }; 370 371 this.$get = ['$http', '$q', function($http, $q) { 372 373 var noop = angular.noop, 374 forEach = angular.forEach, 375 extend = angular.extend, 376 copy = angular.copy, 377 isFunction = angular.isFunction; 378 379 /** 380 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 381 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set 382 * (pchar) allowed in path segments: 383 * segment = *pchar 384 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 385 * pct-encoded = "%" HEXDIG HEXDIG 386 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 387 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 388 * / "*" / "+" / "," / ";" / "=" 389 */ 390 function encodeUriSegment(val) { 391 return encodeUriQuery(val, true). 392 replace(/%26/gi, '&'). 393 replace(/%3D/gi, '='). 394 replace(/%2B/gi, '+'); 395 } 396 397 398 /** 399 * This method is intended for encoding *key* or *value* parts of query component. We need a 400 * custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't 401 * have to be encoded per http://tools.ietf.org/html/rfc3986: 402 * query = *( pchar / "/" / "?" ) 403 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 404 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 405 * pct-encoded = "%" HEXDIG HEXDIG 406 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 407 * / "*" / "+" / "," / ";" / "=" 408 */ 409 function encodeUriQuery(val, pctEncodeSpaces) { 410 return encodeURIComponent(val). 411 replace(/%40/gi, '@'). 412 replace(/%3A/gi, ':'). 413 replace(/%24/g, '$'). 414 replace(/%2C/gi, ','). 415 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 416 } 417 418 function Route(template, defaults) { 419 this.template = template; 420 this.defaults = extend({}, provider.defaults, defaults); 421 this.urlParams = {}; 422 } 423 424 Route.prototype = { 425 setUrlParams: function(config, params, actionUrl) { 426 var self = this, 427 url = actionUrl || self.template, 428 val, 429 encodedVal; 430 431 var urlParams = self.urlParams = {}; 432 forEach(url.split(/\W/), function(param) { 433 if (param === 'hasOwnProperty') { 434 throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name."); 435 } 436 if (!(new RegExp("^\\d+$").test(param)) && param && 437 (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) { 438 urlParams[param] = true; 439 } 440 }); 441 url = url.replace(/\\:/g, ':'); 442 443 params = params || {}; 444 forEach(self.urlParams, function(_, urlParam) { 445 val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; 446 if (angular.isDefined(val) && val !== null) { 447 encodedVal = encodeUriSegment(val); 448 url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), function(match, p1) { 449 return encodedVal + p1; 450 }); 451 } else { 452 url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match, 453 leadingSlashes, tail) { 454 if (tail.charAt(0) == '/') { 455 return tail; 456 } else { 457 return leadingSlashes + tail; 458 } 459 }); 460 } 461 }); 462 463 // strip trailing slashes and set the url (unless this behavior is specifically disabled) 464 if (self.defaults.stripTrailingSlashes) { 465 url = url.replace(/\/+$/, '') || '/'; 466 } 467 468 // then replace collapse `/.` if found in the last URL path segment before the query 469 // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x` 470 url = url.replace(/\/\.(?=\w+($|\?))/, '.'); 471 // replace escaped `/\.` with `/.` 472 config.url = url.replace(/\/\\\./, '/.'); 473 474 475 // set params - delegate param encoding to $http
476 forEach(params, function(value, key) { 477 if (!self.urlParams[key]) { 478 config.params = config.params || {}; 479 config.params[key] = value; 480 } 481 }); 482 } 483 }; 484 485 486 function resourceFactory(url, paramDefaults, actions, options) { 487 var route = new Route(url, options); 488 489 actions = extend({}, provider.defaults.actions, actions); 490 491 function extractParams(data, actionParams) { 492 var ids = {}; 493 actionParams = extend({}, paramDefaults, actionParams); 494 forEach(actionParams, function(value, key) { 495 if (isFunction(value)) { value = value(); } 496 ids[key] = value && value.charAt && value.charAt(0) == '@' ? 497 lookupDottedPath(data, value.substr(1)) : value; 498 }); 499 return ids; 500 } 501 502 function defaultResponseInterceptor(response) { 503 return response.resource; 504 } 505 506 function Resource(value) { 507 shallowClearAndCopy(value || {}, this); 508 } 509 510 Resource.prototype.toJSON = function() { 511 var data = extend({}, this); 512 delete data.$promise; 513 delete data.$resolved; 514 return data; 515 }; 516 517 forEach(actions, function(action, name) { 518 var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method); 519 520 Resource[name] = function(a1, a2, a3, a4) { 521 var params = {}, data, success, error; 522 523 /* jshint -W086 */ /* (purposefully fall through case statements) */ 524 switch (arguments.length) { 525 case 4: 526 error = a4; 527 success = a3; 528 //fallthrough 529 case 3: 530 case 2: 531 if (isFunction(a2)) { 532 if (isFunction(a1)) { 533 success = a1; 534 error = a2; 535 break; 536 } 537 538 success = a2; 539 error = a3; 540 //fallthrough 541 } else { 542 params = a1; 543 data = a2; 544 success = a3; 545 break; 546 } 547 case 1: 548 if (isFunction(a1)) success = a1; 549 else if (hasBody) data = a1; 550 else params = a1; 551 break; 552 case 0: break; 553 default: 554 throw $resourceMinErr('badargs', 555 "Expected up to 4 arguments [params, data, success, error], got {0} arguments", 556 arguments.length); 557 } 558 /* jshint +W086 */ /* (purposefully fall through case statements) */ 559 560 var isInstanceCall = this instanceof Resource; 561 var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data)); 562 var httpConfig = {}; 563 var responseInterceptor = action.interceptor && action.interceptor.response || 564 defaultResponseInterceptor; 565 var responseErrorInterceptor = action.interceptor && action.interceptor.responseError || 566 undefined; 567 568 forEach(action, function(value, key) { 569 if (key != 'params' && key != 'isArray' && key != 'interceptor') { 570 httpConfig[key] = copy(value); 571 } 572 }); 573 574 if (hasBody) httpConfig.data = data; 575 route.setUrlParams(httpConfig, 576 extend({}, extractParams(data, action.params || {}), params), 577 action.url); 578 579 var promise = $http(httpConfig).then(function(response) { 580 var data = response.data, 581 promise = value.$promise; 582 583 if (data) { 584 // Need to convert action.isArray to boolean in case it is undefined 585 // jshint -W018 586 if (angular.isArray(data) !== (!!action.isArray)) { 587 throw $resourceMinErr('badcfg', 588 'Error in resource configuration for action `{0}`. Expected response to ' + 589 'contain an {1} but got an {2}', name, action.isArray ? 'array' : 'object', 590 angular.isArray(data) ? 'array' : 'object'); 591 } 592 // jshint +W018 593 if (action.isArray) { 594 value.length = 0;
595 forEach(data, function(item) { 596 if (typeof item === "object") { 597 value.push(new Resource(item)); 598 } else { 599 // Valid JSON values may be string literals, and these should not be converted 600 // into objects. These items will not have access to the Resource prototype 601 // methods, but unfortunately there 602 value.push(item); 603 } 604 }); 605 } else { 606 shallowClearAndCopy(data, value); 607 value.$promise = promise; 608 } 609 } 610 611 value.$resolved = true; 612 613 response.resource = value; 614 615 return response; 616 }, function(response) { 617 value.$resolved = true; 618 619 (error || noop)(response); 620 621 return $q.reject(response); 622 }); 623 624 promise = promise.then( 625 function(response) { 626 var value = responseInterceptor(response); 627 (success || noop)(value, response.headers); 628 return value; 629 }, 630 responseErrorInterceptor); 631 632 if (!isInstanceCall) { 633 // we are creating instance / collection 634 // - set the initial promise 635 // - return the instance / collection 636 value.$promise = promise; 637 value.$resolved = false; 638 639 return value; 640 } 641 642 // instance call 643 return promise; 644 }; 645 646 647 Resource.prototype['$' + name] = function(params, success, error) { 648 if (isFunction(params)) { 649 error = success; success = params; params = {}; 650 } 651 var result = Resource[name].call(this, params, this, success, error); 652 return result.$promise || result; 653 }; 654 }); 655 656 Resource.bind = function(additionalParamDefaults) { 657 return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); 658 }; 659 660 return Resource; 661 } 662 663 return resourceFactory; 664 }]; 665 }); 666 667 668})(window, window.angular);
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.