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