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