1/* ======================================================================== 2 * OctoberCMS: front-end JavaScript framework 3 * http://octobercms.com 4 * ======================================================================== 5 * Copyright 2016-2020 Alexey Bobkov, Samuel Georges 6 * ======================================================================== */ 7 8if (window.jQuery === undefined) { 9 throw new Error('The jQuery library is not loaded. The OctoberCMS framework cannot be initialized.'); 10} 11if (window.jQuery.request !== undefined) { 12 throw new Error('The OctoberCMS framework is already loaded.'); 13} 14 15+function ($) { "use strict"; 16 17 var Request = function (element, handler, options) { 18 var $el = this.$el = $(element); 19 this.options = options || {}; 20 21 /* 22 * Validate handler name 23 */ 24 if (handler === undefined) { 25 throw new Error('The request handler name is not specified.') 26 } 27 28 if (!handler.match(/^(?:\w+\:{2})?on*/)) { 29 throw new Error('Invalid handler name. The correct handler name format is: "onEvent".') 30 } 31 32 /* 33 * Prepare the options and execute the request 34 */ 35 var $form = options.form ? $(options.form) : $el.closest('form'), 36 $triggerEl = !!$form.length ? $form : $el, 37 context = { handler: handler, options: options } 38 39 $el.trigger('ajaxSetup', [context]) 40 var _event = jQuery.Event('oc.beforeRequest') 41 $triggerEl.trigger(_event, context) 42 if (_event.isDefaultPrevented()) return 43 44 var loading = options.loading !== undefined ? options.loading : null, 45 url = options.url !== undefined ? options.url : window.location.href, 46 isRedirect = options.redirect !== undefined && options.redirect.length, 47 useFlash = options.flash !== undefined, 48 useFiles = options.files !== undefined 49 50 if (useFiles && typeof FormData === 'undefined') { 51 console.warn('This browser does not support file uploads via FormData') 52 useFiles = false 53 } 54 55 if ($.type(loading) == 'string') { 56 loading = $(loading) 57 } 58 59 /* 60 * Request headers 61 */ 62 var requestHeaders = { 63 'X-OCTOBER-REQUEST-HANDLER': handler, 64 'X-OCTOBER-REQUEST-PARTIALS': this.extractPartials(options.update) 65 } 66 67 if (useFlash) { 68 requestHeaders['X-OCTOBER-REQUEST-FLASH'] = 1 69 } 70 71 var csrfToken = getXSRFToken() 72 if (csrfToken) { 73 requestHeaders['X-XSRF-TOKEN'] = csrfToken 74 } 75 76 /* 77 * Request data 78 */ 79 var requestData, 80 inputName, 81 data = {} 82 83 $.each($el.parents('[data-request-data]').toArray().reverse(), function extendRequest() { 84 $.extend(data, paramToObj('data-request-data', $(this).data('request-data'))) 85 }) 86 87 if ($el.is(':input') && !$form.length) { 88 inputName = $el.attr('name') 89 if (inputName !== undefined && options.data[inputName] === undefined) { 90 options.data[inputName] = $el.val() 91 } 92 } 93 94 if (options.data !== undefined && !$.isEmptyObject(options.data)) { 95 $.extend(data, options.data) 96 } 97 98 if (useFiles) { 99 requestData = new FormData($form.length ? $form.get(0) : undefined) 100 101 if ($el.is(':file') && inputName) { 102 $.each($el.prop('files'), function() { 103 requestData.append(inputName, this) 104 }) 105 106 delete data[inputName] 107 } 108 109 $.each(data, function(key) { 110 requestData.append(key, this) 111 }) 112 } 113 else { 114 requestData = [$form.serialize(), $.param(data)].filter(Boolean).join('&') 115 } 116 117 /* 118 * Request options 119 */ 120 var requestOptions = { 121 url: url, 122 crossDomain: false, 123 global: options.ajaxGlobal, 124 context: context, 125 headers: requestHeaders, 126 success: function(data, textStatus, jqXHR) { 127 /* 128 * Halt here if beforeUpdate() or data-request-before-update returns false 129 */ 130 if (this.options.beforeUpdate.apply(this, [data, textStatus, jqXHR]) === false) return 131 if (options.evalBeforeUpdate && eval('(function($el, context, data, textStatus, jqXHR) {'+options.evalBeforeUpdate+'}.call($el.get(0), $el, context, data, textStatus, jqXHR))') === false) return 132 133 /* 134 * Trigger 'ajaxBeforeUpdate' on the form, halt if event.preventDefault() is called 135 */ 136 var _event = jQuery.Event('ajaxBeforeUpdate') 137 $triggerEl.trigger(_event, [context, data, textStatus, jqXHR]) 138 if (_event.isDefaultPrevented()) return 139 140 if (useFlash && data['X_OCTOBER_FLASH_MESSAGES']) { 141 $.each(data['X_OCTOBER_FLASH_MESSAGES'], function(type, message) { 142 requestOptions.handleFlashMessage(message, type) 143 }) 144 } 145 146 /* 147 * Proceed with the update process 148 */ 149 var updatePromise = requestOptions.handleUpdateResponse(data, textStatus, jqXHR) 150 151 updatePromise.done(function() {
152 $triggerEl.trigger('ajaxSuccess', [context, data, textStatus, jqXHR]) 153 options.evalSuccess && eval('(function($el, context, data, textStatus, jqXHR) {'+options.evalSuccess+'}.call($el.get(0), $el, context, data, textStatus, jqXHR))') 154 }) 155 156 return updatePromise 157 }, 158 error: function(jqXHR, textStatus, errorThrown) { 159 var errorMsg, 160 updatePromise = $.Deferred() 161 162 if ((window.ocUnloading !== undefined && window.ocUnloading) || errorThrown == 'abort') 163 return 164 165 /* 166 * Disable redirects 167 */ 168 isRedirect = false 169 options.redirect = null 170 171 /* 172 * Error 406 is a "smart error" that returns response object that is 173 * processed in the same fashion as a successful response. 174 */ 175 if (jqXHR.status == 406 && jqXHR.responseJSON) { 176 errorMsg = jqXHR.responseJSON['X_OCTOBER_ERROR_MESSAGE'] 177 updatePromise = requestOptions.handleUpdateResponse(jqXHR.responseJSON, textStatus, jqXHR) 178 } 179 /* 180 * Standard error with standard response text 181 */ 182 else { 183 errorMsg = jqXHR.responseText ? jqXHR.responseText : jqXHR.statusText 184 updatePromise.resolve() 185 } 186 187 updatePromise.done(function() { 188 $el.data('error-message', errorMsg) 189 190 /* 191 * Trigger 'ajaxError' on the form, halt if event.preventDefault() is called 192 */ 193 var _event = jQuery.Event('ajaxError') 194 $triggerEl.trigger(_event, [context, errorMsg, textStatus, jqXHR]) 195 if (_event.isDefaultPrevented()) return 196 197 /* 198 * Halt here if the data-request-error attribute returns false 199 */ 200 if (options.evalError && eval('(function($el, context, errorMsg, textStatus, jqXHR) {'+options.evalError+'}.call($el.get(0), $el, context, errorMsg, textStatus, jqXHR))') === false) 201 return 202 203 requestOptions.handleErrorMessage(errorMsg) 204 }) 205 206 return updatePromise 207 }, 208 complete: function(data, textStatus, jqXHR) { 209 $triggerEl.trigger('ajaxComplete', [context, data, textStatus, jqXHR]) 210 options.evalComplete && eval('(function($el, context, data, textStatus, jqXHR) {'+options.evalComplete+'}.call($el.get(0), $el, context, data, textStatus, jqXHR))') 211 }, 212 213 /* 214 * Custom function, requests confirmation from the user 215 */ 216 handleConfirmMessage: function(message) { 217 var _event = jQuery.Event('ajaxConfirmMessage') 218 219 _event.promise = $.Deferred() 220 if ($(window).triggerHandler(_event, [message]) !== undefined) { 221 _event.promise.done(function() { 222 options.confirm = null 223 new Request(element, handler, options) 224 }) 225 return false 226 } 227 228 if (_event.isDefaultPrevented()) return 229 if (message) return confirm(message) 230 }, 231 232 /* 233 * Custom function, display an error message to the user 234 */ 235 handleErrorMessage: function(message) { 236 var _event = jQuery.Event('ajaxErrorMessage') 237 $(window).trigger(_event, [message]) 238 if (_event.isDefaultPrevented()) return 239 if (message) alert(message) 240 }, 241 242 /* 243 * Custom function, focus fields with errors 244 */ 245 handleValidationMessage: function(message, fields) {
246 $triggerEl.trigger('ajaxValidation', [context, message, fields]) 247 248 var isFirstInvalidField = true 249 $.each(fields, function focusErrorField(fieldName, fieldMessages) { 250 fieldName = fieldName.replace(/\.(\w+)/g, '[$1]') 251 252 var fieldElement = $form.find('[name="'+fieldName+'"], [name="'+fieldName+'[]"], [name$="['+fieldName+']"], [name$="['+fieldName+'][]"]').filter(':enabled').first() 253 if (fieldElement.length > 0) { 254 255 var _event = jQuery.Event('ajaxInvalidField') 256 $(window).trigger(_event, [fieldElement.get(0), fieldName, fieldMessages, isFirstInvalidField]) 257 258 if (isFirstInvalidField) { 259 if (!_event.isDefaultPrevented()) fieldElement.focus() 260 isFirstInvalidField = false 261 } 262 } 263 }) 264 }, 265 266 /* 267 * Custom function, display a flash message to the user 268 */ 269 handleFlashMessage: function(message, type) {}, 270 271 /* 272 * Custom function, redirect the browser to another location 273 */ 274 handleRedirectResponse: function(url) { 275 window.location.assign(url) 276 }, 277 278 /* 279 * Custom function, handle any application specific response values 280 * Using a promisary object here in case injected assets need time to load 281 */ 282 handleUpdateResponse: function(data, textStatus, jqXHR) { 283 284 /* 285 * Update partials and finish request 286 */ 287 var updatePromise = $.Deferred().done(function() { 288 for (var partial in data) { 289 /* 290 * If a partial has been supplied on the client side that matches the server supplied key, look up 291 * it's selector and use that. If not, we assume it is an explicit selector reference. 292 */ 293 var selector = (options.update[partial]) ? options.update[partial] : partial 294 if ($.type(selector) == 'string' && selector.charAt(0) == '@') { 295 $(selector.substring(1)).append(data[partial]).trigger('ajaxUpdate', [context, data, textStatus, jqXHR]) 296 } 297 else if ($.type(selector) == 'string' && selector.charAt(0) == '^') { 298 $(selector.substring(1)).prepend(data[partial]).trigger('ajaxUpdate', [context, data, textStatus, jqXHR]) 299 } 300 else { 301 $(selector).trigger('ajaxBeforeReplace') 302 $(selector).html(data[partial]).trigger('ajaxUpdate', [context, data, textStatus, jqXHR]) 303 } 304 } 305 306 /* 307 * Wait for .html() method to finish rendering from partial updates 308 */ 309 setTimeout(function() { 310 $(window) 311 .trigger('ajaxUpdateComplete', [context, data, textStatus, jqXHR]) 312 .trigger('resize') 313 }, 0) 314 }) 315 316 /* 317 * Handle redirect 318 */ 319 if (data['X_OCTOBER_REDIRECT']) { 320 options.redirect = data['X_OCTOBER_REDIRECT'] 321 isRedirect = true 322 } 323 324 if (isRedirect) { 325 requestOptions.handleRedirectResponse(options.redirect) 326 } 327 328 /* 329 * Handle validation 330 */ 331 if (data['X_OCTOBER_ERROR_FIELDS']) { 332 requestOptions.handleValidationMessage(data['X_OCTOBER_ERROR_MESSAGE'], data['X_OCTOBER_ERROR_FIELDS']) 333 } 334 335 /* 336 * Handle asset injection 337 */ 338 if (data['X_OCTOBER_ASSETS']) { 339 assetManager.load(data['X_OCTOBER_ASSETS'], $.proxy(updatePromise.resolve, updatePromise)) 340 } 341 else { 342 updatePromise.resolve() 343 } 344 345 return updatePromise 346 } 347 } 348 349 if (useFiles) { 350 requestOptions.processData = requestOptions.contentType = false 351 } 352 353 /* 354 * Allow default business logic to be called from user functions 355 */ 356 context.success = requestOptions.success 357 context.error = requestOptions.error 358 context.complete = requestOptions.complete 359 requestOptions = $.extend(requestOptions, options) 360 requestOptions.data = requestData 361 362 /* 363 * Initiate request 364 */ 365 if (options.confirm && !requestOptions.handleConfirmMessage(options.confirm)) { 366 return 367 } 368 369 if (loading) loading.show()
370 $(window).trigger('ajaxBeforeSend', [context]) 371 $el.trigger('ajaxPromise', [context]) 372 373 return $.ajax(requestOptions) 374 .fail(function(jqXHR, textStatus, errorThrown) { 375 if (!isRedirect) { 376 $el.trigger('ajaxFail', [context, textStatus, jqXHR]) 377 } 378 if (loading) loading.hide() 379 }) 380 .done(function(data, textStatus, jqXHR) { 381 if (!isRedirect) { 382 $el.trigger('ajaxDone', [context, data, textStatus, jqXHR]) 383 } 384 if (loading) loading.hide() 385 }) 386 .always(function(dataOrXhr, textStatus, xhrOrError) { 387 $el.trigger('ajaxAlways', [context, dataOrXhr, textStatus, xhrOrError]) 388 }) 389 } 390 391 Request.DEFAULTS = { 392 update: {}, 393 type : 'POST', 394 beforeUpdate: function(data, textStatus, jqXHR) {}, 395 evalBeforeUpdate: null, 396 evalSuccess: null, 397 evalError: null, 398 evalComplete: null, 399 ajaxGlobal: false 400 } 401 402 /* 403 * Internal function, build a string of partials and their update elements. 404 */ 405 Request.prototype.extractPartials = function(update) { 406 var result = [] 407 408 for (var partial in update) 409 result.push(partial) 410 411 return result.join('&') 412 } 413 414 // REQUEST PLUGIN DEFINITION 415 // ============================ 416 417 var old = $.fn.request 418 419 $.fn.request = function(handler, option) { 420 var args = arguments 421 422 var $this = $(this).first() 423 var data = { 424 evalBeforeUpdate: $this.data('request-before-update'), 425 evalSuccess: $this.data('request-success'), 426 evalError: $this.data('request-error'), 427 evalComplete: $this.data('request-complete'), 428 ajaxGlobal: $this.data('request-ajax-global'), 429 confirm: $this.data('request-confirm'), 430 redirect: $this.data('request-redirect'), 431 loading: $this.data('request-loading'), 432 flash: $this.data('request-flash'), 433 files: $this.data('request-files'), 434 form: $this.data('request-form'), 435 url: $this.data('request-url'), 436 update: paramToObj('data-request-update', $this.data('request-update')), 437 data: paramToObj('data-request-data', $this.data('request-data')) 438 } 439 if (!handler) handler = $this.data('request') 440 var options = $.extend(true, {}, Request.DEFAULTS, data, typeof option == 'object' && option) 441 return new Request($this, handler, options) 442 } 443 444 $.fn.request.Constructor = Request 445 446 $.request = function(handler, option) { 447 return $(document).request(handler, option) 448 } 449 450 // REQUEST NO CONFLICT 451 // ================= 452 453 $.fn.request.noConflict = function() { 454 $.fn.request = old 455 return this 456 } 457 458 // REQUEST DATA-API 459 // ============== 460 461 function paramToObj(name, value) { 462 if (value === undefined) value = '' 463 if (typeof value == 'object') return value 464 465 try { 466 return ocJSON("{" + value + "}") 467 } 468 catch (e) { 469 throw new Error('Error parsing the '+name+' attribute value. '+e) 470 } 471 } 472 473 function getXSRFToken() { 474 var cookieValue = null 475 if (document.cookie && document.cookie != '') { 476 var cookies = document.cookie.split(';') 477 for (var i = 0; i < cookies.length; i++) { 478 var cookie = jQuery.trim(cookies[i]) 479 if (cookie.substring(0, 11) == ('XSRF-TOKEN' + '=')) { 480 cookieValue = decodeURIComponent(cookie.substring(11)) 481 break 482 } 483 } 484 } 485 return cookieValue 486 } 487 488 $(document).on('change', 'select[data-request], input[type=radio][data-request], input[type=checkbox][data-request], input[type=file][data-request]', function documentOnChange() { 489 $(this).request() 490 }) 491 492 $(document).on('click', 'a[data-request], button[data-request], input[type=button][data-request], input[type=submit][data-request]', function documentOnClick(e) { 493 e.preventDefault() 494 495 $(this).request() 496 497 if ($(this).is('[type=submit]')) 498 return false 499 }) 500 501 $(document).on('keydown', 'input[type=text][data-request], input[type=submit][data-request], input[type=password][data-request]', function documentOnKeydown(e) { 502 if (e.key === 'Enter') { 503 if (this.dataTrackInputTimer !== undefined) 504 window.clearTimeout(this.dataTrackInputTimer) 505 506 $(this).request() 507 return false 508 } 509 }) 510 511 $(document).on('input', 'input[data-request][data-track-input]', function documentOnKeyup(e) { 512 var 513 $el = $(this),
514 lastValue = $el.data('oc.lastvalue') 515 516 if (!$el.is('[type=email],[type=number],[type=password],[type=search],[type=text]')) 517 return 518 519 if (lastValue !== undefined && lastValue == this.value) 520 return 521 522 $el.data('oc.lastvalue', this.value) 523 524 if (this.dataTrackInputTimer !== undefined) 525 window.clearTimeout(this.dataTrackInputTimer) 526 527 var interval = $(this).data('track-input') 528 if (!interval) 529 interval = 300 530 531 var self = this 532 this.dataTrackInputTimer = window.setTimeout(function() { 533 if (self.lastDataTrackInputRequest) { 534 self.lastDataTrackInputRequest.abort(); 535 } 536 self.lastDataTrackInputRequest = $(self).request(); 537 }, interval) 538 }) 539 540 $(document).on('submit', '[data-request]', function documentOnSubmit() { 541 $(this).request() 542 return false 543 }) 544 545 $(window).on('beforeunload', function documentOnBeforeUnload() { 546 window.ocUnloading = true 547 }) 548 549 /* 550 * Invent our own event that unifies document.ready with window.ajaxUpdateComplete 551 * 552 * $(document).render(function() { }) 553 * $(document).on('render', function() { }) 554 */ 555 556 $(document).ready(function triggerRenderOnReady() { 557 $(document).trigger('render') 558 }) 559 560 $(window).on('ajaxUpdateComplete', function triggerRenderOnAjaxUpdateComplete() { 561 $(document).trigger('render') 562 }) 563 564 $.fn.render = function(callback) { 565 $(document).on('render', callback) 566 } 567 568}(window.jQuery); 569 570/* 571 * October CMS JSON Parser 572 */ 573+function(window) { "use strict"; 574 function parseKey(str, pos, quote) { 575 var key = ""; 576 for (var i = pos; i < str.length; i++) { 577 if (quote && quote === str[i]) { 578 return key; 579 } else if (!quote && (str[i] === " " || str[i] === ":")) { 580 return key; 581 } 582 583 key += str[i]; 584 585 if (str[i] === "\\" && i + 1 < str.length) { 586 key += str[i + 1]; 587 i++; 588 } 589 } 590 throw new Error("Broken JSON syntax near " + key); 591 } 592 593 function getBody(str, pos) { 594 // parse string body 595 if (str[pos] === "\"" || str[pos] === "'") { 596 var body = str[pos]; 597 for (var i = pos + 1; i < str.length; i++) { 598 if (str[i] === "\\") { 599 body += str[i]; 600 if (i + 1 < str.length) body += str[i + 1]; 601 i++; 602 } else if (str[i] === str[pos]) { 603 body += str[pos]; 604 return { 605 originLength: body.length, 606 body: body 607 }; 608 } else body += str[i]; 609 } 610 throw new Error("Broken JSON string body near " + body); 611 } 612 613 // parse true / false 614 if (str[pos] === "t") { 615 if (str.indexOf("true", pos) === pos) { 616 return { 617 originLength: "true".length, 618 body: "true" 619 }; 620 } 621 throw new Error("Broken JSON boolean body near " + str.substr(0, pos + 10)); 622 } 623 if (str[pos] === "f") { 624 if (str.indexOf("f", pos) === pos) { 625 return { 626 originLength: "false".length, 627 body: "false" 628 }; 629 } 630 throw new Error("Broken JSON boolean body near " + str.substr(0, pos + 10)); 631 } 632 633 // parse null 634 if (str[pos] === "n") { 635 if (str.indexOf("null", pos) === pos) { 636 return { 637 originLength: "null".length, 638 body: "null" 639 }; 640 } 641 throw new Error("Broken JSON boolean body near " + str.substr(0, pos + 10)); 642 } 643 644 // parse number 645 if (str[pos] === "-" || str[pos] === "+" || str[pos] === "." || (str[pos] >= "0" && str[pos] <= "9")) { 646 var body = ""; 647 for (var i = pos; i < str.length; i++) { 648 if (str[i] === "-" || str[i] === "+" || str[i] === "." || (str[i] >= "0" && str[i] <= "9")) { 649 body += str[i]; 650 } else { 651 return { 652 originLength: body.length, 653 body: body 654 }; 655 } 656 } 657 throw new Error("Broken JSON number body near " + body); 658 } 659 660 // parse object 661 if (str[pos] === "{" || str[pos] === "[") { 662 var stack = [str[pos]]; 663 var body = str[pos]; 664 for (var i = pos + 1; i < str.length; i++) { 665 body += str[i]; 666 if (str[i] === "\\") { 667 if (i + 1 < str.length) body += str[i + 1]; 668 i++; 669 } else if (str[i] === "\"") { 670 if (stack[stack.length - 1] === "\"") { 671 stack.pop(); 672 } else if (stack[stack.length - 1] !== "'") {
673 stack.push(str[i]); 674 } 675 } else if (str[i] === "'") { 676 if (stack[stack.length - 1] === "'") { 677 stack.pop(); 678 } else if (stack[stack.length - 1] !== "\"") { 679 stack.push(str[i]); 680 } 681 } else if (stack[stack.length - 1] !== "\"" && stack[stack.length - 1] !== "'") { 682 if (str[i] === "{") { 683 stack.push("{"); 684 } else if (str[i] === "}") { 685 if (stack[stack.length - 1] === "{") { 686 stack.pop(); 687 } else { 688 throw new Error("Broken JSON " + (str[pos] === "{" ? "object" : "array") + " body near " + body); 689 } 690 } else if (str[i] === "[") { 691 stack.push("["); 692 } else if (str[i] === "]") { 693 if (stack[stack.length - 1] === "[") { 694 stack.pop(); 695 } else { 696 throw new Error("Broken JSON " + (str[pos] === "{" ? "object" : "array") + " body near " + body); 697 } 698 } 699 } 700 if (!stack.length) { 701 return { 702 originLength: i - pos, 703 body: body 704 }; 705 } 706 } 707 throw new Error("Broken JSON " + (str[pos] === "{" ? "object" : "array") + " body near " + body); 708 } 709 throw new Error("Broken JSON body near " + str.substr((pos - 5 >= 0) ? pos - 5 : 0, 50)); 710 } 711 712 function canBeKeyHead(ch) { 713 if (ch[0] === "\\") return false; 714 if ((ch[0] >= 'a' && ch[0] <= 'z') || (ch[0] >= 'A' && ch[0] <= 'Z') || ch[0] === '_') return true; 715 if (ch[0] >= '0' && ch[0] <= '9') return true; 716 if (ch[0] === '$') return true; 717 if (ch.charCodeAt(0) > 255) return true; 718 return false; 719 } 720 721 function isBlankChar(ch) { 722 return ch === " " || ch === "\n" || ch === "\t"; 723 } 724 725 function parse(str) { 726 str = str.trim(); 727 if (!str.length) throw new Error("Broken JSON object."); 728 var result = ""; 729 730 /* 731 * the mistake ',' 732 */ 733 while (str && str[0] === ",") { 734 str = str.substr(1); 735 } 736 737 /* 738 * string 739 */ 740 if (str[0] === "\"" || str[0] === "'") { 741 if (str[str.length - 1] !== str[0]) { 742 throw new Error("Invalid string JSON object."); 743 } 744 745 var body = "\""; 746 for (var i = 1; i < str.length; i++) { 747 if (str[i] === "\\") { 748 if (str[i + 1] === "'") { 749 body += str[i + 1] 750 } else { 751 body += str[i]; 752 body += str[i + 1]; 753 } 754 i++; 755 } else if (str[i] === str[0]) { 756 body += "\""; 757 return body 758 } else if (str[i] === "\"") { 759 body += "\\\"" 760 } else body += str[i]; 761 } 762 throw new Error("Invalid string JSON object."); 763 } 764 765 /* 766 * boolean 767 */ 768 if (str === "true" || str === "false") { 769 return str; 770 } 771 772 /* 773 * null 774 */ 775 if (str === "null") { 776 return "null"; 777 } 778 779 /* 780 * number 781 */ 782 var num = parseFloat(str); 783 if (!isNaN(num)) { 784 return num.toString(); 785 } 786 787 /* 788 * object 789 */ 790 if (str[0] === "{") { 791 var type = "needKey"; 792 var result = "{"; 793 794 for (var i = 1; i < str.length; i++) { 795 if (isBlankChar(str[i])) { 796 continue; 797 } else if (type === "needKey" && (str[i] === "\"" || str[i] === "'")) { 798 var key = parseKey(str, i + 1, str[i]); 799 result += "\"" + key + "\""; 800 i += key.length; 801 i += 1; 802 type = "afterKey"; 803 } else if (type === "needKey" && canBeKeyHead(str[i])) { 804 var key = parseKey(str, i); 805 result += "\""; 806 result += key; 807 result += "\""; 808 i += key.length - 1; 809 type = "afterKey"; 810 } else if (type === "afterKey" && str[i] === ":") { 811 result += ":"; 812 type = ":"; 813 } else if (type === ":") { 814 var body = getBody(str, i); 815 816 i = i + body.originLength - 1; 817 result += parse(body.body); 818 819 type = "afterBody"; 820 } else if (type === "afterBody" || type === "needKey") { 821 var last = i; 822 while (str[last] === "," || isBlankChar(str[last])) { 823 last++; 824 } 825 if (str[last] === "}" && last === str.length - 1) { 826 while (result[result.length - 1] === ",") { 827 result = result.substr(0, result.length - 1); 828 } 829 result += "}"; 830 return result; 831 } else if (last !== i && result !== "{") { 832 result += ","; 833 type = "needKey"; 834 i = last - 1; 835 } 836 } 837 } 838 throw new Error("Broken JSON object near " + result); 839 } 840 841 /* 842 * array 843 */ 844 if (str[0] === "[") { 845 var result = "["; 846 var type = "needBody"; 847 for (var i = 1; i < str.length; i++) { 848 if (" " === str[i] || "\n" === str[i] || "\t" === str[i]) { 849 continue; 850 } else if (type === "needBody") { 851 if (str[i] === ",") { 852 result += "null,"; 853 continue; 854 } 855 if (str[i] === "]" && i === str.length - 1) { 856 if (result[result.length - 1] === ",") result = result.substr(0, result.length - 1); 857 result += "]"; 858 return result; 859 } 860 861 var body = getBody(str, i); 862 863 i = i + body.originLength - 1; 864 result += parse(body.body); 865 866 type = "afterBody"; 867 } else if (type === "afterBody") { 868 if (str[i] === ",") { 869 result += ","; 870 type = "needBody"; 871 872 // deal with mistake "," 873 while (str[i + 1] === "," || isBlankChar(str[i + 1])) { 874 if (str[i + 1] === ",") result += "null,"; 875 i++; 876 } 877 } else if (str[i] === "]" && i === str.length - 1) { 878 result += "]"; 879 return result; 880 } 881 } 882 } 883 throw new Error("Broken JSON array near " + result); 884 } 885 } 886 887 // Global function 888 window.ocJSON = function(json) { 889 var jsonString = parse(json); 890 return JSON.parse(jsonString); 891 }; 892 893}(window);
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.