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