- // ==UserScript==
- // @name WME Utils - Google Link Enhancer
- // @namespace WazeDev
- // @version 2018.03.14.001
- // @description Adds some extra WME functionality related to Google place links.
- // @author MapOMatic, WazeDev group
- // @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor\/?.*$/
- // @license GNU GPLv3
- // ==/UserScript==
-
- class GoogleLinkEnhancer {
-
- constructor() {
- this.EXT_PROV_ELEM_QUERY = 'li.external-provider-item';
- this.LINK_CACHE_NAME = 'gle_link_cache';
- this.LINK_CACHE_CLEAN_INTERVAL_MIN = 1; // Interval to remove old links and save new ones.
- this.LINK_CACHE_LIFESPAN_HR = 6; // Remove old links when they exceed this time limit.
- this._enabled = false;
- this._mapLayer = null;
- this._urlOrigin = window.location.origin;
- this._distanceLimit = 400; // Default distance (meters) when Waze place is flagged for being too far from Google place.
- // Area place is calculated as _distanceLimit + <distance between centroid and furthest node>
-
- this.strings = {};
- this.strings.closedPlace = 'Google indicates this place is permanently closed.\nVerify with other sources or your editor community before deleting.';
- this.strings.multiLinked = 'Linked more than once already. Please find and remove multiple links.';
- this.strings.linkedToThisPlace = 'Already linked to this place';
- this.strings.linkedNearby = 'Already linked to a nearby place';
- this.strings.linkedToXPlaces = 'This is linked to {0} places';
- this.strings.badLink = 'Invalid Google link. Please remove it.';
- this.strings.tooFar = 'The Google linked place is more than {0} meters from the Waze place. Please verify the link is correct.';
-
- this._initLZString();
-
- let storedCache = localStorage.getItem(this.LINK_CACHE_NAME);
- this._linkCache = storedCache ? $.parseJSON(this._LZString.decompress(storedCache)) : {};
- if (this._linkCache === null) this._linkCache = {};
-
- this._initLayer();
-
- // Watch for ext provider elements being added to the DOM, and add hover events.
- this._linkObserver = new MutationObserver(mutations => {
- mutations.forEach(mutation => {
- for (let idx=0; idx<mutation.addedNodes.length; idx++) {
- let nd = mutation.addedNodes[idx];
- if (nd.nodeType === Node.ELEMENT_NODE) {
- let $el = $(nd);
- if ($el.is(this.EXT_PROV_ELEM_QUERY)) {
- this._addHoverEvent($el);
- } else {
- if ($el.find('div.uuid').length) {
- this._formatLinkElements();
- }
- }
- }
- }
- });
- });
-
- // Watch the side panel for addition of the sidebar-layout div, which indicates a mode change.
- this._modeObserver = new MutationObserver(mutations => {
- mutations.forEach(mutation => {
- for (let idx = 0; idx<mutation.addedNodes.length; idx++) {
- let nd = mutation.addedNodes[idx];
- if (nd.nodeType === Node.ELEMENT_NODE && $(nd).is('.sidebar-layout')) {
- this._observeLinks();
- break;
- }
- }
- });
- });
-
- // This is a special event that will be triggered when DOM elements are destroyed.
- (function($){
- $.event.special.destroyed = {
- remove: function(o) {
- if (o.handler && o.type !== 'destroyed') {
- o.handler();
- }
- }
- };
- })(jQuery);
- }
-
-
- _initLayer(){
- this._mapLayer = new OpenLayers.Layer.Vector('Google Link Enhancements.', {
- uniqueName: '___GoogleLinkEnhancements',
- displayInLayerSwitcher: true,
- styleMap: new OpenLayers.StyleMap({
- default: {
- strokeColor: '${strokeColor}',
- strokeWidth: '${strokeWidth}',
- strokeDashstyle: '${strokeDashstyle}',
- pointRadius: '15',
- fillOpacity: '0'
- }
- })
- });
-
- this._mapLayer.setOpacity(0.8);
-
- W.map.addLayer(this._mapLayer);
-
- W.model.events.register('mergeend',this,function(e){
- this._processPlaces();
- },true);
- W.map.events.register('moveend',this,function(e){
- this._processPlaces();
- },true);
- W.model.venues.on('objectschanged', function(e) {
- this._processPlaces();
- }, this);
- }
-
- enable() {
- this._enabled = true;
- this._modeObserver.observe($('.edit-area #sidebarContent')[0], {childList: true, subtree:false});
- this._observeLinks();
- // Note: Using on() allows passing "this" as a variable, so it can be used in the handler function.
- $(document).on('ajaxSuccess', null, this, this._onAjaxSuccess);
- $('#map').on('mouseenter', null, this, this._onMapMouseenter);
- $(window).on('unload', null, this, this._onWindowUnload);
- W.model.venues.on('objectschanged', this._formatLinkElements, this);
- this._processPlaces();
- this._cleanAndSaveLinkCache();
- this._cacheCleanIntervalID = setInterval(() => this._cleanAndSaveLinkCache(), 1000 * 60 * this.LINK_CACHE_CLEAN_INTERVAL_MIN);
- }
-
- disable() {
- this._enabled = false;
- this._modeObserver.disconnect();
- this._linkObserver.disconnect();
- $(document).off('ajaxSuccess', this._onAjaxSuccess);
- $('#map').off('mouseenter', this._onMapMouseenter);
- $(window).off('unload', null, this, this._onWindowUnload);
- W.model.venues.off('objectschanged', this._formatLinkElements, this);
- if (this._cacheCleanIntervalID) clearInterval(this._cacheCleanIntervalID);
- this._cleanAndSaveLinkCache();
- this._mapLayer.removeAllFeatures();
- }
-
- // The distance (in meters) before flagging a Waze place that is too far from the linked Google place.
- // Area places use distanceLimit, plus the distance from the centroid of the AP to its furthest node.
- get distanceLimit() {
- return this._distanceLimit;
- }
- set distanceLimit(value) {
- this._distanceLimit = value;
- this._processPlaces();
- }
-
- _onWindowUnload(evt) {
- evt.data._cleanAndSaveLinkCache();
- }
-
- _cleanAndSaveLinkCache() {
- if (!this._linkCache) return;
- let now = new Date();
- Object.keys(this._linkCache).forEach(id => {
- let link = this._linkCache[id];
- // Bug fix:
- if (link.location) {
- link.loc = link.location;
- delete link.location;
- }
- // Delete link if older than X hours.
- if (!link.ts || (now - new Date(link.ts)) > this.LINK_CACHE_LIFESPAN_HR * 3600 * 1000) {
- delete this._linkCache[id];
- }
- });
- localStorage.setItem(this.LINK_CACHE_NAME, this._LZString.compress(JSON.stringify(this._linkCache)));
- //console.log('link cache count: ' + Object.keys(this._linkCache).length, this._linkCache);
- }
-
- _distanceBetweenPoints(x1, y1, x2, y2) {
- return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
- }
-
- _isLinkTooFar(link, venue) {
- if (link.loc) {
- let linkPt = new OL.Geometry.Point(link.loc.lng, link.loc.lat);
- linkPt.transform(W.map.displayProjection, W.map.projection);
- let venuePt;
- let distanceLimit;
- if (venue.isPoint()) {
- venuePt = venue.geometry.getCentroid();
- distanceLimit = this.distanceLimit;
- } else {
- let bounds = venue.geometry.getBounds();
- let center = bounds.getCenterLonLat();
- venuePt = {x: center.lon, y: center.lat};
- distanceLimit = this._distanceBetweenPoints(center.lon, center.lat, bounds.right, bounds.top) + this.distanceLimit;
- }
- let distance = this._distanceBetweenPoints(linkPt.x, linkPt.y, venuePt.x, venuePt.y);
-
- return distance > distanceLimit;
- } else {
- return false;
- }
- }
-
- _processPlaces() {
- try {
- if (this._enabled) {
- let that = this;
- let projFrom = W.map.displayProjection;
- let projTo = W.map.projection;
- let mapExtent = W.map.getExtent();
- this._mapLayer.removeAllFeatures();
- W.model.venues.getObjectArray().forEach(function(venue) {
- let isTooFar = false;
- venue.attributes.externalProviderIDs.forEach(provID => {
- let id = provID.attributes.uuid;
- that._getLinkInfoAsync(id).then(link => {
- // Check for distance from Google POI.
- if (that._isLinkTooFar(link, venue) && !isTooFar) {
- isTooFar = true;
- let venuePt = venue.geometry.getCentroid();
- let dashStyle = 'solid'; //venue.isPoint() ? '2 6' : '2 16';
- let geometry = venue.isPoint() ? venuePt : venue.geometry.clone();
- let width = venue.isPoint() ? '4' : '12';
- that._mapLayer.addFeatures([new OpenLayers.Feature.Vector(geometry, {strokeWidth:width, strokeColor:'#0FF', strokeDashstyle:dashStyle})]);
- }
-
- // Check for closed places or invalid Google links.
- if (link.closed || link.notFound) {
- let dashStyle = link.closed && (/^(\[|\()?(permanently )?closed(\]|\)| -)/i.test(venue.attributes.name) || /(\(|- |\[)(permanently )?closed(\)|\])?$/i.test(venue.attributes.name)) ? (venue.isPoint() ? '2 6' : '2 16') : 'solid';
- let geometry = venue.isPoint() ? venue.geometry.getCentroid() : venue.geometry.clone();
- let width = venue.isPoint() ? '4' : '12';
- let color = link.notFound ? '#F0F' : '#F00';
- that._mapLayer.addFeatures([new OpenLayers.Feature.Vector(geometry, {strokeWidth:width, strokeColor:color, strokeDashstyle:dashStyle})]);
- }
- }).catch(res => {
- console.log(res);
- });
- });
- });
- }
- } catch (ex) {
- console.log(ex);
- }
- }
-
- _cacheLink(id, link) {
- link.ts = new Date();
- this._linkCache[id] = link;
- //console.log('link cache count: ' + Object.keys(this._linkCache).length, this._linkCache);
- }
-
- _getLinkInfoAsync(id) {
- var link = this._linkCache[id];
- if (link) {
- return Promise.resolve(link);
- } else {
- return new Promise((resolve, reject) => {
- $.getJSON(this._urlOrigin + '/maps/api/place/details/json?&key=AIzaSyDf-q2MCay0AE7RF6oIMrDPrjBwxVtsUuI&placeid=' + id).then(json => {
- if (json.status==='NOT_FOUND') {
- link = {notFound: true};
- } else {
- link = {loc:json.result.geometry.location,closed:json.result.permanently_closed};
- }
- this._cacheLink(id, link);
- resolve(link);
- }).fail(res => {
- reject(res);
- });
- });
- }
- }
-
- _onMapMouseenter(event) {
- // If the point isn't destroyed yet, destroy it when mousing over the map.
- event.data._destroyPoint();
- }
-
- _formatLinkElements(a,b,c) {
- let existingLinks = this._getExistingLinks();
- $('#edit-panel').find(this.EXT_PROV_ELEM_QUERY).each((ix, childEl) => {
- let $childEl = $(childEl);
- let id = this._getIdFromElement($childEl);
- if (existingLinks[id] && existingLinks[id].count > 1 && existingLinks[id].isThisVenue) {
- setTimeout(() => {
- $childEl.find('div.uuid').css({backgroundColor:'#FFA500'}).attr({'title':this.strings.linkedToXPlaces.replace('{0}', existingLinks[id].count)});
- }, 50);
- }
- this._addHoverEvent($(childEl));
-
- let link = this._linkCache[id];
- if (link) {
- if (link.closed) {
- // A delay is needed to allow the UI to do its formatting so it doesn't overwrite ours.
- setTimeout(() => {
- $childEl.find('div.uuid').css({backgroundColor:'#FAA'}).attr('title',this.strings.closedPlace);
- }, 50);
- } else if (link.notFound) {
- setTimeout(() => {
- $childEl.find('div.uuid').css({backgroundColor:'#F0F'}).attr('title',this.strings.badLink);
- }, 50);
- } else {
- let venue = W.selectionManager.selectedItems[0].model;
- if (this._isLinkTooFar(link, venue)) {
- setTimeout(() => {
- $childEl.find('div.uuid').css({backgroundColor:'#0FF'}).attr('title',this.strings.tooFar.replace('{0}',this.distanceLimit));
- }, 50);
- }
- }
- }
- });
- }
-
- _getExistingLinks() {
- let existingLinks = {};
- let thisVenue;
- if (W.selectionManager.selectedItems.length) {
- thisVenue = W.selectionManager.selectedItems[0].model;
- }
- W.model.venues.getObjectArray().forEach(venue => {
- let isThisVenue = venue === thisVenue;
- let thisPlaceIDs = [];
- venue.attributes.externalProviderIDs.forEach(provID => {
- let id = provID.attributes.uuid;
- if (thisPlaceIDs.indexOf(id) === -1) {
- thisPlaceIDs.push(id);
- let link = existingLinks[id];
- if (link) {
- link.count++;
- } else {
- link = {count: 1};
- existingLinks[id] = link;
- }
- link.isThisVenue = link.isThisVenue || isThisVenue;
- }
- });
- });
- return existingLinks;
- }
-
- _onAjaxSuccess(event, jqXHR, ajaxOptions, data) {
- let url = ajaxOptions.url;
- let that = event.data;
-
- if(/^\/maps\/api\/place\/autocomplete\/json?/i.test(url)) {
- // After an "autocomplete" api call...
-
- // Get a list of already-linked id's
- let existingLinks = that._getExistingLinks();
-
- // Examine the suggestions and format any that are already linked.
- $('#select2-drop ul li').each((idx, el) => {
- let $el = $(el);
- let linkData = $el.data('select2Data');
- if (linkData) {
- let link = existingLinks[linkData.id];
- if (link) {
- let title, bgColor, textColor, fontWeight;
- if (link.count > 1) {
- title = that.strings.multiLinked;
- textColor = '#000';
- bgColor = '#FFA500';
- } else {
- bgColor = '#ddd';
- if (link.isThisVenue) {
- title = that.strings.linkedToThisPlace;
- textColor = '#444';
- fontWeight = 600;
- } else {
- title = that.strings.linkedNearby;
- textColor = '#888';
- }
- }
- if (bgColor) $el.css({backgroundColor: bgColor});
- if (textColor) $el.css({color: textColor});
- if (fontWeight) $el.css({fontWeight: fontWeight});
- $el.attr('title',title);
- }
- $el.mouseover(function() {
- that._addPoint(linkData.id);
- }).mouseleave(() => that._destroyPoint()).bind('destroyed', () => that._destroyPoint()).mousedown(() => that._destroyPoint());
- }
- });
- } else if (/^\/maps\/api\/place\/details\/json?/i.test(url)) {
- //After a "details" api call...
-
- // Cache location results. Note this isn't absolutely necessary because they're
- // cached when calling for them on mouseover. However, WME calls this api for each link
- // when the place is first loaded, so might as well take advantage of it.
-
- let link = {};
- if (data.status === 'NOT_FOUND') {
- link.notFound = true;
- } else {
- link.loc = data.result.geometry.location;
- link.closed = data.result.permanently_closed;
- }
- var id = url.match(/placeid=(.*)&?/)[0];
- that._cacheLink(id, link);
- that._formatLinkElements();
- }
- }
-
- // Remove the POI point from the map.
- _destroyPoint() {
- if (this._feature) this._feature.destroy();
- this._feature = null;
- }
-
- // Add the POI point to the map.
- _addPoint(id) {
- if (!id) return;
- let link = this._linkCache[id];
- if (link) {
- if (!link.notFound) {
- let coord = link.loc;
- let pt = new OpenLayers.Geometry.Point(coord.lng, coord.lat);
- pt.transform(W.map.displayProjection, W.map.projection);
- if ( pt.intersects(W.map.getExtent().toGeometry()) ) {
- this._destroyPoint(); // Just in case it still exists.
- this._feature = new OL.Feature.Vector(pt,{poiCoord:true},{
- pointRadius: 6,
- strokeWidth: 30,
- strokeColor: '#FF0',
- fillColor: '#FF0',
- strokeOpacity: 0.5
- });
- W.map.getLayerByUniqueName('landmarks').addFeatures([this._feature]);
- this._timeoutDestroyPoint();
- }
- }
- } else {
- $.getJSON(this._urlOrigin + '/maps/api/place/details/json?&key=AIzaSyDf-q2MCay0AE7RF6oIMrDPrjBwxVtsUuI&placeid=' + id).then(json => {
- this._cacheLink(id, {loc:json.result.geometry.location,closed:json.result.permanently_closed});
- this._addPoint(id);
- });
- }
- }
-
- // Destroy the point after some time, if it hasn't been destroyed already.
- _timeoutDestroyPoint() {
- if (this._timeoutID) clearTimeout(this._timeoutID);
- this._timeoutID = setTimeout(() => this._destroyPoint(), 4000);
- }
-
- _getIdFromElement($el) {
- return $el.find('input.uuid').attr('value');
- }
-
- _addHoverEvent($el) {
- $el.hover(() => this._addPoint(this._getIdFromElement($el)) , () => this._destroyPoint());
- }
-
- _observeLinks() {
- this._linkObserver.observe($('#edit-panel')[0],{ childList: true, subtree: true });
- }
-
- _initLZString() {
- // LZ Compressor
- // Copyright (c) 2013 Pieroxy <pieroxy@pieroxy.net>
- // This work is free. You can redistribute it and/or modify it
- // under the terms of the WTFPL, Version 2
- // LZ-based compression algorithm, version 1.4.4
- this._LZString = (function() {
- // private property
- var f = String.fromCharCode;
- var keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
- var keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$";
- var baseReverseDic = {};
-
- function getBaseValue(alphabet, character) {
- if (!baseReverseDic[alphabet]) {
- baseReverseDic[alphabet] = {};
- for (var i = 0; i < alphabet.length; i++) {
- baseReverseDic[alphabet][alphabet.charAt(i)] = i;
- }
- }
- return baseReverseDic[alphabet][character];
- }
- var LZString = {
- compressToBase64: function(input) {
- if (input === null) return "";
- var res = LZString._compress(input, 6, function(a) {
- return keyStrBase64.charAt(a);
- });
- switch (res.length % 4) { // To produce valid Base64
- default: // When could this happen ?
- case 0:
- return res;
- case 1:
- return res + "===";
- case 2:
- return res + "==";
- case 3:
- return res + "=";
- }
- },
- decompressFromBase64: function(input) {
- if (input === null) return "";
- if (input === "") return null;
- return LZString._decompress(input.length, 32, function(index) {
- return getBaseValue(keyStrBase64, input.charAt(index));
- });
- },
- compressToUTF16: function(input) {
- if (input === null) return "";
- return LZString._compress(input, 15, function(a) {
- return f(a + 32);
- }) + " ";
- },
- decompressFromUTF16: function(compressed) {
- if (compressed === null) return "";
- if (compressed === "") return null;
- return LZString._decompress(compressed.length, 16384, function(index) {
- return compressed.charCodeAt(index) - 32;
- });
- },
-
- compress: function(uncompressed) {
- return LZString._compress(uncompressed, 16, function(a) {
- return f(a);
- });
- },
- _compress: function(uncompressed, bitsPerChar, getCharFromInt) {
- if (uncompressed === null) return "";
- var i, value,
- context_dictionary = {},
- context_dictionaryToCreate = {},
- context_c = "",
- context_wc = "",
- context_w = "",
- context_enlargeIn = 2, // Compensate for the first entry which should not count
- context_dictSize = 3,
- context_numBits = 2,
- context_data = [],
- context_data_val = 0,
- context_data_position = 0,
- ii;
- for (ii = 0; ii < uncompressed.length; ii += 1) {
- context_c = uncompressed.charAt(ii);
- if (!Object.prototype.hasOwnProperty.call(context_dictionary, context_c)) {
- context_dictionary[context_c] = context_dictSize++;
- context_dictionaryToCreate[context_c] = true;
- }
- context_wc = context_w + context_c;
- if (Object.prototype.hasOwnProperty.call(context_dictionary, context_wc)) {
- context_w = context_wc;
- } else {
- if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
- if (context_w.charCodeAt(0) < 256) {
- for (i = 0; i < context_numBits; i++) {
- context_data_val = (context_data_val << 1);
- if (context_data_position === bitsPerChar - 1) {
- context_data_position = 0;
- context_data.push(getCharFromInt(context_data_val));
- context_data_val = 0;
- } else {
- context_data_position++;
- }
- }
- value = context_w.charCodeAt(0);
- for (i = 0; i < 8; i++) {
- context_data_val = (context_data_val << 1) | (value & 1);
- if (context_data_position === bitsPerChar - 1) {
- context_data_position = 0;
- context_data.push(getCharFromInt(context_data_val));
- context_data_val = 0;
- } else {
- context_data_position++;
- }
- value = value >> 1;
- }
- } else {
- value = 1;
- for (i = 0; i < context_numBits; i++) {
- context_data_val = (context_data_val << 1) | value;
- if (context_data_position === bitsPerChar - 1) {
- context_data_position = 0;
- context_data.push(getCharFromInt(context_data_val));
- context_data_val = 0;
- } else {
- context_data_position++;
- }
- value = 0;
- }
- value = context_w.charCodeAt(0);
- for (i = 0; i < 16; i++) {
- context_data_val = (context_data_val << 1) | (value & 1);
- if (context_data_position === bitsPerChar - 1) {
- context_data_position = 0;
- context_data.push(getCharFromInt(context_data_val));
- context_data_val = 0;
- } else {
- context_data_position++;
- }
- value = value >> 1;
- }
- }
- context_enlargeIn--;
- if (context_enlargeIn === 0) {
- context_enlargeIn = Math.pow(2, context_numBits);
- context_numBits++;
- }
- delete context_dictionaryToCreate[context_w];
- } else {
- value = context_dictionary[context_w];
- for (i = 0; i < context_numBits; i++) {
- context_data_val = (context_data_val << 1) | (value & 1);
- if (context_data_position === bitsPerChar - 1) {
- context_data_position = 0;
- context_data.push(getCharFromInt(context_data_val));
- context_data_val = 0;
- } else {
- context_data_position++;
- }
- value = value >> 1;
- }
- }
- context_enlargeIn--;
- if (context_enlargeIn === 0) {
- context_enlargeIn = Math.pow(2, context_numBits);
- context_numBits++;
- }
- // Add wc to the dictionary.
- context_dictionary[context_wc] = context_dictSize++;
- context_w = String(context_c);
- }
- }
- // Output the code for w.
- if (context_w !== "") {
- if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
- if (context_w.charCodeAt(0) < 256) {
- for (i = 0; i < context_numBits; i++) {
- context_data_val = (context_data_val << 1);
- if (context_data_position === bitsPerChar - 1) {
- context_data_position = 0;
- context_data.push(getCharFromInt(context_data_val));
- context_data_val = 0;
- } else {
- context_data_position++;
- }
- }
- value = context_w.charCodeAt(0);
- for (i = 0; i < 8; i++) {
- context_data_val = (context_data_val << 1) | (value & 1);
- if (context_data_position === bitsPerChar - 1) {
- context_data_position = 0;
- context_data.push(getCharFromInt(context_data_val));
- context_data_val = 0;
- } else {
- context_data_position++;
- }
- value = value >> 1;
- }
- } else {
- value = 1;
- for (i = 0; i < context_numBits; i++) {
- context_data_val = (context_data_val << 1) | value;
- if (context_data_position === bitsPerChar - 1) {
- context_data_position = 0;
- context_data.push(getCharFromInt(context_data_val));
- context_data_val = 0;
- } else {
- context_data_position++;
- }
- value = 0;
- }
- value = context_w.charCodeAt(0);
- for (i = 0; i < 16; i++) {
- context_data_val = (context_data_val << 1) | (value & 1);
- if (context_data_position === bitsPerChar - 1) {
- context_data_position = 0;
- context_data.push(getCharFromInt(context_data_val));
- context_data_val = 0;
- } else {
- context_data_position++;
- }
- value = value >> 1;
- }
- }
- context_enlargeIn--;
- if (context_enlargeIn === 0) {
- context_enlargeIn = Math.pow(2, context_numBits);
- context_numBits++;
- }
- delete context_dictionaryToCreate[context_w];
- } else {
- value = context_dictionary[context_w];
- for (i = 0; i < context_numBits; i++) {
- context_data_val = (context_data_val << 1) | (value & 1);
- if (context_data_position === bitsPerChar - 1) {
- context_data_position = 0;
- context_data.push(getCharFromInt(context_data_val));
- context_data_val = 0;
- } else {
- context_data_position++;
- }
- value = value >> 1;
- }
- }
- context_enlargeIn--;
- if (context_enlargeIn === 0) {
- context_enlargeIn = Math.pow(2, context_numBits);
- context_numBits++;
- }
- }
- // Mark the end of the stream
- value = 2;
- for (i = 0; i < context_numBits; i++) {
- context_data_val = (context_data_val << 1) | (value & 1);
- if (context_data_position === bitsPerChar - 1) {
- context_data_position = 0;
- context_data.push(getCharFromInt(context_data_val));
- context_data_val = 0;
- } else {
- context_data_position++;
- }
- value = value >> 1;
- }
- // Flush the last char
- while (true) {
- context_data_val = (context_data_val << 1);
- if (context_data_position === bitsPerChar - 1) {
- context_data.push(getCharFromInt(context_data_val));
- break;
- } else context_data_position++;
- }
- return context_data.join('');
- },
- decompress: function(compressed) {
- if (compressed === null) return "";
- if (compressed === "") return null;
- return LZString._decompress(compressed.length, 32768, function(index) {
- return compressed.charCodeAt(index);
- });
- },
- _decompress: function(length, resetValue, getNextValue) {
- var dictionary = [],
- next,
- enlargeIn = 4,
- dictSize = 4,
- numBits = 3,
- entry = "",
- result = [],
- i,
- w,
- bits, resb, maxpower, power,
- c,
- data = {
- val: getNextValue(0),
- position: resetValue,
- index: 1
- };
- for (i = 0; i < 3; i += 1) {
- dictionary[i] = i;
- }
- bits = 0;
- maxpower = Math.pow(2, 2);
- power = 1;
- while (power !== maxpower) {
- resb = data.val & data.position;
- data.position >>= 1;
- if (data.position === 0) {
- data.position = resetValue;
- data.val = getNextValue(data.index++);
- }
- bits |= (resb > 0 ? 1 : 0) * power;
- power <<= 1;
- }
- switch (next = bits) {
- case 0:
- bits = 0;
- maxpower = Math.pow(2, 8);
- power = 1;
- while (power !== maxpower) {
- resb = data.val & data.position;
- data.position >>= 1;
- if (data.position === 0) {
- data.position = resetValue;
- data.val = getNextValue(data.index++);
- }
- bits |= (resb > 0 ? 1 : 0) * power;
- power <<= 1;
- }
- c = f(bits);
- break;
- case 1:
- bits = 0;
- maxpower = Math.pow(2, 16);
- power = 1;
- while (power !== maxpower) {
- resb = data.val & data.position;
- data.position >>= 1;
- if (data.position === 0) {
- data.position = resetValue;
- data.val = getNextValue(data.index++);
- }
- bits |= (resb > 0 ? 1 : 0) * power;
- power <<= 1;
- }
- c = f(bits);
- break;
- case 2:
- return "";
- }
- dictionary[3] = c;
- w = c;
- result.push(c);
- while (true) {
- if (data.index > length) {
- return "";
- }
- bits = 0;
- maxpower = Math.pow(2, numBits);
- power = 1;
- while (power !== maxpower) {
- resb = data.val & data.position;
- data.position >>= 1;
- if (data.position === 0) {
- data.position = resetValue;
- data.val = getNextValue(data.index++);
- }
- bits |= (resb > 0 ? 1 : 0) * power;
- power <<= 1;
- }
- switch (c = bits) {
- case 0:
- bits = 0;
- maxpower = Math.pow(2, 8);
- power = 1;
- while (power !== maxpower) {
- resb = data.val & data.position;
- data.position >>= 1;
- if (data.position === 0) {
- data.position = resetValue;
- data.val = getNextValue(data.index++);
- }
- bits |= (resb > 0 ? 1 : 0) * power;
- power <<= 1;
- }
- dictionary[dictSize++] = f(bits);
- c = dictSize - 1;
- enlargeIn--;
- break;
- case 1:
- bits = 0;
- maxpower = Math.pow(2, 16);
- power = 1;
- while (power !== maxpower) {
- resb = data.val & data.position;
- data.position >>= 1;
- if (data.position === 0) {
- data.position = resetValue;
- data.val = getNextValue(data.index++);
- }
- bits |= (resb > 0 ? 1 : 0) * power;
- power <<= 1;
- }
- dictionary[dictSize++] = f(bits);
- c = dictSize - 1;
- enlargeIn--;
- break;
- case 2:
- return result.join('');
- }
- if (enlargeIn === 0) {
- enlargeIn = Math.pow(2, numBits);
- numBits++;
- }
- if (dictionary[c]) {
- entry = dictionary[c];
- } else {
- if (c === dictSize) {
- entry = w + w.charAt(0);
- } else {
- return null;
- }
- }
- result.push(entry);
- // Add w+entry[0] to the dictionary.
- dictionary[dictSize++] = w + entry.charAt(0);
- enlargeIn--;
- w = entry;
- if (enlargeIn === 0) {
- enlargeIn = Math.pow(2, numBits);
- numBits++;
- }
- }
- }
- };
- return LZString;
- })();
- }
- }