diff options
Diffstat (limited to 'www/wiki/extensions/Maps/resources/GoogleMaps')
-rw-r--r-- | www/wiki/extensions/Maps/resources/GoogleMaps/ext.maps.googlemaps3.js | 31 | ||||
-rw-r--r-- | www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ProjectedOverlay.js | 186 | ||||
-rw-r--r-- | www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/README | 2 | ||||
-rw-r--r-- | www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ZipFile.complete.js | 3939 | ||||
-rw-r--r-- | www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/geoxml3.js | 3398 | ||||
-rw-r--r-- | www/wiki/extensions/Maps/resources/GoogleMaps/googlemaps3ajax.js (renamed from www/wiki/extensions/Maps/resources/GoogleMaps/ext.sm.googlemaps3ajax.js) | 2 | ||||
-rw-r--r-- | www/wiki/extensions/Maps/resources/GoogleMaps/jquery.googlemap.js | 10 |
7 files changed, 3935 insertions, 3633 deletions
diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/ext.maps.googlemaps3.js b/www/wiki/extensions/Maps/resources/GoogleMaps/ext.maps.googlemaps3.js index ce5880c2..8a7b9b49 100644 --- a/www/wiki/extensions/Maps/resources/GoogleMaps/ext.maps.googlemaps3.js +++ b/www/wiki/extensions/Maps/resources/GoogleMaps/ext.maps.googlemaps3.js @@ -1,25 +1,16 @@ -/** - * JavaScript for Google Maps v3 maps in the Maps extension. - * @see https://www.mediawiki.org/wiki/Extension:Maps - * - * @licence GNU GPL v2+ - * @author Jeroen De Dauw <jeroendedauw at gmail dot com> - */ -(function( $, mw ) { - - $( document ).ready( function() { +window.mapsGoogleList = []; - if ( typeof google === 'undefined' ) { - $( '.maps-googlemaps3' ).text( mw.msg( 'maps-googlemaps3-incompatbrowser' ) ); - } - else { - $( '.maps-googlemaps3' ).each( function() { +(function( $, mw ) { + mw.hook( 'wikipage.content' ).add( function ( $content ) { + if( typeof google === 'undefined' ) { + $content.find( '.maps-googlemaps3' ).text( mw.msg( 'maps-googlemaps3-incompatbrowser' ) ); + } else { + $content.find( '.maps-googlemaps3' ).each( function() { var $this = $( this ); - var map = $this.googlemaps( $.parseJSON( $this.find( 'div').text() ) ); - window.maps.googlemapsList.push(map); + window.mapsGoogleList.push( + $this.googlemaps( JSON.parse( $this.find( 'div.mapdata' ).text() ) ) + ); } ); } - } ); - -})( window.jQuery, mediaWiki ); +})( window.jQuery, window.mediaWiki ); diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ProjectedOverlay.js b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ProjectedOverlay.js index 4056c2c3..d655ed9e 100644 --- a/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ProjectedOverlay.js +++ b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ProjectedOverlay.js @@ -13,127 +13,139 @@ // addZoom: Added Zoom factor as a parameter to the imageUrl (include complete parameter, including separater like '?zoom=' // percentOpacity: Default 50, percent opacity to use when the image is loaded 0-100. // id: Default imageUrl, ID of the div +// rotation: default 0, degrees clockwise to rotate the image // - + function ProjectedOverlay(map, imageUrl, bounds, opts) { - google.maps.OverlayView.call(this); - - this.map_ = map; - this.url_ = imageUrl ; - this.bounds_ = bounds ; - this.addZ_ = opts.addZoom || '' ; // Add the zoom to the image as a parameter - this.id_ = opts.id || this.url_ ; // Added to allow for multiple images - this.percentOpacity_ = opts.percentOpacity || 50 ; - - this.setMap(map); + google.maps.OverlayView.call(this); + + this.map_ = map; + this.url_ = imageUrl ; + this.bounds_ = bounds ; + this.addZ_ = opts.addZoom || '' ; // Add the zoom to the image as a parameter + this.id_ = opts.id || this.url_ ; // Added to allow for multiple images + this.percentOpacity_ = opts.percentOpacity || 50 ; + this.rotation_ = opts.rotation || 0; + this.setMap(map); } ProjectedOverlay.prototype = new google.maps.OverlayView(); ProjectedOverlay.prototype.createElement = function() { - var panes = this.getPanes() ; - var div = this.div_ ; - - if (!div) - { - div = this.div_ = document.createElement("div"); - div.style.position = "absolute" ; - div.setAttribute('id',this.id_) ; - this.div_ = div ; - this.lastZoom_ = -1 ; - if( this.percentOpacity_ ) - { - this.setOpacity(this.percentOpacity_) ; - } - panes.overlayLayer.appendChild(div); - } + var panes = this.getPanes() ; + var div = this.div_ ; + + if (!div) + { + div = this.div_ = document.createElement("div"); + div.style.position = "absolute" ; + div.setAttribute('id',this.id_) ; + this.div_ = div ; + this.lastZoom_ = -1 ; + if( this.percentOpacity_ ) + { + this.setOpacity(this.percentOpacity_) ; + } + if ( this.rotation_ ) + { + this.setRotation(this.rotation_) ; + } + panes.overlayLayer.appendChild(div); + } } // Remove the main DIV from the map pane ProjectedOverlay.prototype.remove = function() { - if (this.div_) - { - this.div_.parentNode.removeChild(this.div_); - this.div_ = null; - this.setMap(null); - } + if (this.div_) + { + this.div_.parentNode.removeChild(this.div_); + this.div_ = null; + this.setMap(null); + } } // Redraw based on the current projection and zoom level... ProjectedOverlay.prototype.draw = function(firstTime) { - // Creates the element if it doesn't exist already. + // Creates the element if it doesn't exist already. + + this.createElement(); - this.createElement(); + if (!this.div_) + { + return ; + } - if (!this.div_) - { - return ; - } + var c1 = this.get('projection').fromLatLngToDivPixel(this.bounds_.getSouthWest()); + var c2 = this.get('projection').fromLatLngToDivPixel(this.bounds_.getNorthEast()); - var c1 = this.get('projection').fromLatLngToDivPixel(this.bounds_.getSouthWest()); - var c2 = this.get('projection').fromLatLngToDivPixel(this.bounds_.getNorthEast()); + if (!c1 || !c2) return; - if (!c1 || !c2) return; + // Now position our DIV based on the DIV coordinates of our bounds - // Now position our DIV based on the DIV coordinates of our bounds + this.div_.style.width = Math.abs(c2.x - c1.x) + "px"; + this.div_.style.height = Math.abs(c2.y - c1.y) + "px"; + this.div_.style.left = Math.min(c2.x, c1.x) + "px"; + this.div_.style.top = Math.min(c2.y, c1.y) + "px"; - this.div_.style.width = Math.abs(c2.x - c1.x) + "px"; - this.div_.style.height = Math.abs(c2.y - c1.y) + "px"; - this.div_.style.left = Math.min(c2.x, c1.x) + "px"; - this.div_.style.top = Math.min(c2.y, c1.y) + "px"; + var url = this.url_ ; - // Do the rest only if the zoom has changed... - - if ( this.lastZoom_ == this.map_.getZoom() ) - { - return ; - } + if ( this.addZ_ ) + { + url += this.addZ_ + this.map_.getZoom() ; + } - this.lastZoom_ = this.map_.getZoom() ; + this.div_.innerHTML = '<img src="' + url + '" width=' + this.div_.style.width + ' height=' + this.div_.style.height + ' >' ; - var url = this.url_ ; + // Do the rest only if the zoom has changed... - if ( this.addZ_ ) - { - url += this.addZ_ + this.map_.getZoom() ; - } + if ( this.lastZoom_ == this.map_.getZoom() ) + { + return ; + } - this.div_.innerHTML = '<img src="' + url + '" width=' + this.div_.style.width + ' height=' + this.div_.style.height + ' >' ; + this.lastZoom_ = this.map_.getZoom() ; } ProjectedOverlay.prototype.setOpacity=function(opacity) { - if (opacity < 0) - { - opacity = 0 ; - } - if(opacity > 100) - { - opacity = 100 ; - } - var c = opacity/100 ; - - if (typeof(this.div_.style.filter) =='string') - { - this.div_.style.filter = 'alpha(opacity:' + opacity + ')' ; - } - if (typeof(this.div_.style.KHTMLOpacity) == 'string' ) - { - this.div_.style.KHTMLOpacity = c ; - } - if (typeof(this.div_.style.MozOpacity) == 'string') - { - this.div_.style.MozOpacity = c ; - } - if (typeof(this.div_.style.opacity) == 'string') - { - this.div_.style.opacity = c ; - } + if (opacity < 0) + { + opacity = 0 ; + } + if(opacity > 100) + { + opacity = 100 ; + } + var c = opacity/100 ; + + if (typeof(this.div_.style.filter) =='string') + { + this.div_.style.filter = 'alpha(opacity:' + opacity + ')' ; + } + if (typeof(this.div_.style.KHTMLOpacity) == 'string' ) + { + this.div_.style.KHTMLOpacity = c ; + } + if (typeof(this.div_.style.MozOpacity) == 'string') + { + this.div_.style.MozOpacity = c ; + } + if (typeof(this.div_.style.opacity) == 'string') + { + this.div_.style.opacity = c ; + } +} +ProjectedOverlay.prototype.setRotation=function(deg) +{ + this.div_.style.webkitTransform = 'rotate('+deg+'deg)'; + this.div_.style.mozTransform = 'rotate('+deg+'deg)'; + this.div_.style.msTransform = 'rotate('+deg+'deg)'; + this.div_.style.oTransform = 'rotate('+deg+'deg)'; + this.div_.style.transform = 'rotate('+deg+'deg)'; } - diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/README b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/README index a82f556e..bcc2ca97 100644 --- a/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/README +++ b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/README @@ -2,4 +2,4 @@ geoxml3.js and ZipFile.complete.js has been fetched from the googlecode project from the branch kmz (which seems to be the branch containing the most features) http://code.google.com/p/geoxml3/source/browse/#svn%2Fbranches%2Fkmz -The latest version fetched when writing this was rev 79.
\ No newline at end of file +The latest version fetched when writing this was https://github.com/geocodezip/geoxml3/tree/a07796fa205d9d40a3c6f898a44324245935984f. diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ZipFile.complete.js b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ZipFile.complete.js index d49bb056..f2f374cf 100644 --- a/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ZipFile.complete.js +++ b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ZipFile.complete.js @@ -19,143 +19,143 @@ (function(){ - if (typeof JSIO == "object"){ - var e1 = new Error("JSIO is already defined"); - e1.source = "JSIO.core.js"; - throw e1; - } - - JSIO = {}; - - JSIO.version = "2.0 2012Feb"; - - JSIO.throwError = function(msg, source, sub) { - var error = new Error("Error: " + msg); - error.source = (source || this._typename || 'JSIO') + (sub ? '.'+sub : ''); - throw error; - } - - // Format a number as hex. Quantities over 7ffffff will be displayed properly. - JSIO.decimalToHexString = function(number, digits) { - if (number < 0) { - number = 0xFFFFFFFF + number + 1; - } - var r1 = number.toString(16).toUpperCase(); - if (digits) { - r1 = "00000000" + r1; - r1 = r1.substring(r1.length - digits); - } - return r1; - }; - - JSIO.FileType = { - Text : 0, - Binary : 1, - XML : 2, - Unknown : 3 - }; - - - JSIO.guessFileType = function(name) { - if (name == "makefile") { return JSIO.FileType.Text; } - - var lastDot = name.lastIndexOf("."); - if (lastDot <= 0) { return JSIO.FileType.Unknown; } - - var ext= name.substring(lastDot); - if (ext == ".zip") { return JSIO.FileType.Binary; } - if (ext == ".xlsx") { return JSIO.FileType.Binary; } - if (ext == ".docx") { return JSIO.FileType.Binary; } - if (ext == ".dll") { return JSIO.FileType.Binary; } - if (ext == ".obj") { return JSIO.FileType.Binary; } - if (ext == ".pdb") { return JSIO.FileType.Binary; } - if (ext == ".exe") { return JSIO.FileType.Binary; } - if (ext == ".kmz") { return JSIO.FileType.Binary; } - - if (ext == ".xml") { return JSIO.FileType.XML; } - if (ext == ".xsl") { return JSIO.FileType.XML; } - if (ext == ".kml") { return JSIO.FileType.XML; } - if (ext == ".csproj") { return JSIO.FileType.XML; } - if (ext == ".vbproj") { return JSIO.FileType.XML; } - if (ext == ".shfbproj") { return JSIO.FileType.XML; } - if (ext == ".resx") { return JSIO.FileType.XML; } - if (ext == ".xslt") { return JSIO.FileType.XML; } - - if (ext == ".sln") { return JSIO.FileType.Text; } - if (ext == ".htm") { return JSIO.FileType.Text; } - if (ext == ".html") { return JSIO.FileType.Text; } - if (ext == ".js") { return JSIO.FileType.Text; } - if (ext == ".vb") { return JSIO.FileType.Text; } - if (ext == ".txt") { return JSIO.FileType.Text; } - if (ext == ".rels") { return JSIO.FileType.Text; } - if (ext == ".css") { return JSIO.FileType.Text; } - if (ext == ".cs") { return JSIO.FileType.Text; } - if (ext == ".asp") { return JSIO.FileType.Text; } - - return JSIO.FileType.Unknown; - }; - - JSIO.stringOfLength = function (charCode, length) { - var s3 = ""; - for (var i = 0; i < length; i++) { - s3 += String.fromCharCode(charCode); - } - return s3; - }; - - JSIO.formatByteArray = function(b) { - var s1 = "0000 "; - var s2 = ""; - for (var i = 0; i < b.length; i++) { - if (i !== 0 && i % 16 === 0) { - s1 += " " + s2 +"\n" + JSIO.decimalToHexString(i, 4) + " "; - s2 = ""; - } - s1 += JSIO.decimalToHexString(b[i], 2) + " "; - if (b[i] >=32 && b[i] <= 126) { - s2 += String.fromCharCode(b[i]); - } else { - s2 += "."; - } - } - if (s2.length > 0) { - s1 += JSIO.stringOfLength(32, ((i%16>0)? ((16 - i%16) * 3) : 0) + 4) + s2; - } - return s1; - }; - - JSIO.htmlEscape = function(str) { - return str - .replace(new RegExp( "&", "g" ), "&") - .replace(new RegExp( "<", "g" ), "<") - .replace(new RegExp( ">", "g" ), ">") - .replace(new RegExp( "\x13", "g" ), "<br/>") - .replace(new RegExp( "\x10", "g" ), "<br/>"); - }; - - JSIO.massApply = function(func, funcThis, arr, needReturn) { - var arrayLimit = 99999; // Chrome has an apply/array limit of 99999; Firefox = 491519 - if (arr.length < arrayLimit) return func.apply(funcThis, arr); - else { - var newThis = funcThis; - var offset = 0; - var end = 99999; - - while (offset < arr.length) { - var arrSlice; - if (arr.subarray) arrSlice = arr.subarray(offset, end); - else if (arr.slice) arrSlice = arr.slice(offset, end); - - if (needReturn) newThis += func.apply(newThis, arrSlice); - else func.apply(funcThis, arrSlice); - - offset += arrayLimit; - end += arrayLimit; - end = Math.min(arr.length, end); - } - return newThis; - } - } + if (typeof JSIO == "object"){ + var e1 = new Error("JSIO is already defined"); + e1.source = "JSIO.core.js"; + throw e1; + } + + JSIO = {}; + + JSIO.version = "2.0 2012Feb"; + + JSIO.throwError = function(msg, source, sub) { + var error = new Error("Error: " + msg); + error.source = (source || this._typename || 'JSIO') + (sub ? '.'+sub : ''); + throw error; + } + + // Format a number as hex. Quantities over 7ffffff will be displayed properly. + JSIO.decimalToHexString = function(number, digits) { + if (number < 0) { + number = 0xFFFFFFFF + number + 1; + } + var r1 = number.toString(16).toUpperCase(); + if (digits) { + r1 = "00000000" + r1; + r1 = r1.substring(r1.length - digits); + } + return r1; + }; + + JSIO.FileType = { + Text : 0, + Binary : 1, + XML : 2, + Unknown : 3 + }; + + + JSIO.guessFileType = function(name) { + if (name == "makefile") { return JSIO.FileType.Text; } + + var lastDot = name.lastIndexOf("."); + if (lastDot <= 0) { return JSIO.FileType.Unknown; } + + var ext= name.substring(lastDot); + if (ext == ".zip") { return JSIO.FileType.Binary; } + if (ext == ".xlsx") { return JSIO.FileType.Binary; } + if (ext == ".docx") { return JSIO.FileType.Binary; } + if (ext == ".dll") { return JSIO.FileType.Binary; } + if (ext == ".obj") { return JSIO.FileType.Binary; } + if (ext == ".pdb") { return JSIO.FileType.Binary; } + if (ext == ".exe") { return JSIO.FileType.Binary; } + if (ext == ".kmz") { return JSIO.FileType.Binary; } + + if (ext == ".xml") { return JSIO.FileType.XML; } + if (ext == ".xsl") { return JSIO.FileType.XML; } + if (ext == ".kml") { return JSIO.FileType.XML; } + if (ext == ".csproj") { return JSIO.FileType.XML; } + if (ext == ".vbproj") { return JSIO.FileType.XML; } + if (ext == ".shfbproj") { return JSIO.FileType.XML; } + if (ext == ".resx") { return JSIO.FileType.XML; } + if (ext == ".xslt") { return JSIO.FileType.XML; } + + if (ext == ".sln") { return JSIO.FileType.Text; } + if (ext == ".htm") { return JSIO.FileType.Text; } + if (ext == ".html") { return JSIO.FileType.Text; } + if (ext == ".js") { return JSIO.FileType.Text; } + if (ext == ".vb") { return JSIO.FileType.Text; } + if (ext == ".txt") { return JSIO.FileType.Text; } + if (ext == ".rels") { return JSIO.FileType.Text; } + if (ext == ".css") { return JSIO.FileType.Text; } + if (ext == ".cs") { return JSIO.FileType.Text; } + if (ext == ".asp") { return JSIO.FileType.Text; } + + return JSIO.FileType.Unknown; + }; + + JSIO.stringOfLength = function (charCode, length) { + var s3 = ""; + for (var i = 0; i < length; i++) { + s3 += String.fromCharCode(charCode); + } + return s3; + }; + + JSIO.formatByteArray = function(b) { + var s1 = "0000 "; + var s2 = ""; + for (var i = 0; i < b.length; i++) { + if (i !== 0 && i % 16 === 0) { + s1 += " " + s2 +"\n" + JSIO.decimalToHexString(i, 4) + " "; + s2 = ""; + } + s1 += JSIO.decimalToHexString(b[i], 2) + " "; + if (b[i] >=32 && b[i] <= 126) { + s2 += String.fromCharCode(b[i]); + } else { + s2 += "."; + } + } + if (s2.length > 0) { + s1 += JSIO.stringOfLength(32, ((i%16>0)? ((16 - i%16) * 3) : 0) + 4) + s2; + } + return s1; + }; + + JSIO.htmlEscape = function(str) { + return str + .replace(new RegExp( "&", "g" ), "&") + .replace(new RegExp( "<", "g" ), "<") + .replace(new RegExp( ">", "g" ), ">") + .replace(new RegExp( "\x13", "g" ), "<br/>") + .replace(new RegExp( "\x10", "g" ), "<br/>"); + }; + + JSIO.massApply = function(func, funcThis, arr, needReturn) { + var arrayLimit = 65536; // Chrome has an apply/array limit of 99999; Firefox = 491519, Safari = 65536 + if (arr.length < arrayLimit) return func.apply(funcThis, arr); + else { + var newThis = funcThis; + var offset = 0; + var end = 65536; + + while (offset < arr.length) { + var arrSlice; + if (arr.subarray) arrSlice = arr.subarray(offset, end); + else if (arr.slice) arrSlice = arr.slice(offset, end); + + if (needReturn) newThis += func.apply(newThis, arrSlice); + else func.apply(funcThis, arrSlice); + + offset += arrayLimit; + end += arrayLimit; + end = Math.min(arr.length, end); + } + return newThis; + } + } })(); @@ -197,364 +197,364 @@ (function(){ - var version = "2.0 2012Feb"; - - if (typeof JSIO !== "object") { JSIO = {}; } - if ((typeof JSIO.version !== "string")) { - JSIO.version = version; - } - else if ((JSIO.version.length < 3) || - (JSIO.version.substring(0,3) !== "2.0")) { - JSIO.version += " " + version; - } - - // ======================================================= - // the base object, used as the prototype of all ByteReader objects. - var _byteReaderBase = function () { - this.position = 0; - // position must be incremented in .readByte() for all derived classes - }; - - _byteReaderBase.prototype._throwError = JSIO.throwError; - - _byteReaderBase.prototype._limitCheck = function(len, startPos) { - var LOE = { - len: len, - pos: startPos, - end: startPos+len - }; - - if (len === 0) return {len:0, pos:0, end:0}; - if (len < 0) this._throwError("Invalid read length"); - if (!this.length) return {len:len, pos:this.position, end:len+this.position}; - if (!startPos >= 0) LOE.pos = this.position; - if (this.length <= LOE.pos) this._throwError("EOF reached"); - - LOE.end = LOE.pos+len; - if (this.length < LOE.end) LOE.end = LOE.pos+(LOE.len = this.length-this.position); - return LOE; - } - - JSIO.SeekOrigin = { - Begin : 0, - Current : 1, - End : 2, - SEEK_SET : 0, - SEEK_CUR : 1, - SEEK_END : 2 - }; - - _byteReaderBase.prototype.seek = function(offset, origin) { - switch (origin) { - case JSIO.SeekOrigin.Begin: - if (offset == this.position) return this.position; - if (!this.length) { - if (offset < this.position) this._throwError('Uni-directional stream cannot seek backwards', null, 'seek'); - else if (offset > this.position) return this.read(offset - this.position); // read will limit check - } - else { - if (this.length < offset) this._throwError('Cannot seek past reader length', null, 'seek'); - this.position = offset; - } - break; - case JSIO.SeekOrigin.Current: - return this.seek(this.position + offset, JSIO.SeekOrigin.Begin); - break; - case JSIO.SeekOrigin.End: - if (!this.length) this._throwError('Uni-directional stream has no known end length for seek', null, 'seek'); - return this.seek(this.length - 1 + offset, JSIO.SeekOrigin.Begin); - break; - default: - this._throwError('Invalid seek method', null, 'seek'); - break; - } - - return this.position; - }; - - _byteReaderBase.prototype.read = function(len, startPos) { - var LOE = this._limitCheck(len, startPos); - if (LOE.len === 0) return []; - if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin); - - var bytesRead = []; - - // Faster methods with an array or stream - if (this.array && this.array.subarray) bytesRead = this.array.subarray(LOE.pos, LOE.end); - else if (this.array && this.array.slice) bytesRead = this.array.slice(LOE.pos, LOE.end); - else if (this.stream) bytesRead = this.stream.read(LOE.len, LOE.pos); - else if (this.length) { // Random-access stream - for(var i=LOE.pos; i<LOE.end; i++) { bytesRead.push(this.readByteAt(i)); } - } - else { // Uni-directional stream - for(var i=LOE.pos; i<LOE.end; i++) { - var b = this.readByte(); - if (b === null || b === undefined) break; - bytesRead.push(b); - } - } - this.position = LOE.end; - return bytesRead; - }; - - _byteReaderBase.prototype.beginRead = function(len, startPos, callback) { - var LOE = this._limitCheck(len, startPos); - if (LOE.len === 0) return setTimeout(function() { callback([]); }, 1); - if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin); - - var bytesRead = []; - var thisReader = this; - var leftToRead = LOE.len; - - var readBatchAsync = function() { - var c = 0; - var pos = thisReader.position; - - // read a 32k batch - var l = (leftToRead >= 32768) ? 32768 : leftToRead; - var newBytes = thisReader.read(l); - JSIO.massApply(bytesRead.push, bytesRead, newBytes); - c += l; - leftToRead -= l; - if (newBytes.length < l) leftToRead = 0; - - if (leftToRead>0) setTimeout(readBatchAsync, 1); - else callback(bytesRead); - }; - - // kickoff - setTimeout(readBatchAsync, 1); // always async, in ALL situations - return null; - }; - - _byteReaderBase.prototype.readToEnd = function() { - if (this.array && this.array.subarray) return this.array.subarray(this.position); - else if (this.array && this.array.slice) return this.array.slice(this.position); - else if (this.length) return this.read(this.length - this.position); - else return this.read(9000 * 9000); // over 9000 - }; - - _byteReaderBase.prototype.beginReadToEnd = function(callback) { - if (this.array && this.array.subarray) setTimeout(function() { callback( this.array.subarray(this.position) ); }, 1); - else if (this.array && this.array.slice) setTimeout(function() { callback( this.array.slice(this.position) ); }, 1); - else if (this.length) return this.beginRead(this.length - this.position, this.position, callback); - else return this.beginRead(9000 * 9000, this.position, callback); - }; - - // Generic routines; one of these two MUST be overloaded (preferrably both) - _byteReaderBase.prototype.readByte = function(){ - if (this.length && this.position >= this.length) return null; // EOF - - var byte; - if (this.array) byte = this.array[this.position++]; - else if (this.length) byte = this.readByteAt(this.position++); - else if (this.stream) byte = this.stream.read(1)[0]; - else byte = this.read(1)[0]; - return (byte === null || byte === undefined) ? null : byte; - }; - _byteReaderBase.prototype.readByteAt = function(i) { - var pos = this.position; // no position changes on this one - if (i === null || i === undefined) i = this.position; - - var byte; - if (this.array) byte = this.array[i]; - else if (i === pos) byte = this.readByte(); - else if (this.stream) byte = this.stream.read(1, i)[0]; - else byte = this.read(1, i)[0]; - - this.position = pos; - return (byte === null || byte === undefined) ? null : byte; - } - - _byteReaderBase.prototype.readBytes = _byteReaderBase.prototype.read; - _byteReaderBase.prototype.beginReadBytes = function(len, callback) { return this.beginRead(len, this.position, callback); }; - - _byteReaderBase.prototype.readNumber = function(len, startPos){ - var LOE = this._limitCheck(len, startPos); - if (LOE.len === 0) LOE.len = 1; - if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin); - - var result = 0; - var bytes = this.read(LOE.len, LOE.pos); - for (var i=bytes.length-1; i>=0; i--) { - // IE only supports 32-bit integer shifting - //result = result << 8 | bytes[i]; - result = result*256 + bytes[i]; - } - return result; - }; - - _byteReaderBase.prototype.readString = function(len, startPos){ - var LOE = this._limitCheck(len, startPos); - if (LOE.len === 0) return ''; - if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin); - - var result = ''; - var bytes = this.read(LOE.len, LOE.pos); - for(var i=0; i<bytes.length; i++){ - result += String.fromCharCode(bytes[i]); - } - return result; - }; - - _byteReaderBase.prototype.readNullTerminatedString = function(startPos){ - var pos = startPos || this.position; - if (this.length && this.length < pos) this._throwError('EOF reached', null, 'readNullTerminatedString'); - if (pos != this.position) this.seek(pos, JSIO.SeekOrigin.Begin); - - var slarge = ""; - var s = ""; - var c = 0; - - // Faster method with an array - if (this.array && this.array.indexOf) { - var len = pos - this.array.indexOf(0, pos); - if (len > 0) return this.readString(len, pos); - } - - var ch; - while(1) { - ch = String.fromCharCode(this.readByteAt(pos+c)); - if (ch === null) break; - - s += ch; - c++; - if(c >= 32768) { - slarge += s; - s = ""; - pos += c; - this.position += c; - c = 0; - } - }; - this.position = pos + c; - return slarge + s; - }; - - _byteReaderBase.prototype.beginReadNullTerminatedString = function(callback, startPos){ - var pos = startPos || this.position; - if (this.length && this.length < pos) this._throwError('EOF reached', null, 'beginReadNullTerminatedString'); - - var slarge = ""; - var s = ""; - var thisBinStream = this; - - var readBatchAsync = function() { - var c = 0; - - var ch; - while(1) { - ch = String.fromCharCode(this.readByteAt(pos+c)); - if (ch === null) break; - - s += ch; - c++; - if(c >= 32768) { - slarge += s; - s = ""; - pos += c; - this.position += c; - c = 0; - } - }; - - thisBinStream.position = pos + c; - if (ch!==null) setTimeout(readBatchAsync, 1); - else callback(slarge+s); - }; - - // Faster method with an array - if (this.array && this.array.indexOf) { - var len = pos - this.array.indexOf(0, pos); - if (len > 0) readBatchASync = function() { callback(thisBinStream.readString(len, pos)); }; - } - - // kickoff - setTimeout(readBatchAsync, 1); // always async, in ALL situations - return null; - }; - - - - JSIO._ByteReaderBase = _byteReaderBase; - // ======================================================= - - - - - // ======================================================= - // reads from an array of bytes. - // This basically wraps a readByte() fn onto array access. - var _arrayReader = function(array) { - if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.ArrayReader', 'ctor'); - this.position = 0; - this.array = array; - this.length = array.length; - this._typename = "JSIO.ArrayReader"; - this._version = version; - return this; - }; - - _arrayReader.prototype = new JSIO._ByteReaderBase(); - - _arrayReader.prototype.readByte = function() { - if (this.position >= this.array.length) return null; // EOF - return this.array[this.position++]; - }; - _arrayReader.prototype.readByteAt = function(i) { - return this.array[i]; - }; - - // ======================================================= - - - // ======================================================= - // reads bytes at a time from a defined segment of a stream. - var _streamSegmentReader = function(stream, offset, len) { - if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.StreamSegmentReader', 'ctor'); - if (!stream) this._throwError('You must pass a non-null stream', 'JSIO.StreamSegmentReader', 'ctor'); - - if (!(offset >= 1)) offset = 0; - if (!(len >= 1)) len = 0; - - this.stream = stream; - this.array = null; - if (stream.array) { - var end = len ? offset + len : null; - if (stream.array.subarray) this.array = stream.array.subarray(offset, end); - else if (stream.array.slice) this.array = stream.array.slice(offset, end); - } - this.length = this.array ? this.array.length : (stream.length ? stream.length - offset : null); - this.offset = offset; - this.limit = len; - this.position = 0; - this._typeName = 'JSIO.StreamSegmentReader'; - this._version = version; - - if (this.array) { - this.readByte = _arrayReader.prototype.readByte; - this.readByteAt = _arrayReader.prototype.readByteAt; - } - return this; - }; - - _streamSegmentReader.prototype = new JSIO._ByteReaderBase(); - - _streamSegmentReader.prototype.readByte = function() { - if (this.limit && this.position >= this.limit) return null; // EOF - this.position++; - return this.stream.readByteAt(this.offset + this.position - 1); - }; - _streamSegmentReader.prototype.readByteAt = function(i) { - if (this.limit && i >= this.limit) return null; // EOF - return this.stream.readByteAt(this.offset + i); - }; - - // ======================================================= - - JSIO.ArrayReader = _arrayReader; - JSIO.StreamReader = _streamSegmentReader; - JSIO.StreamSegmentReader = _streamSegmentReader; + var version = "2.0 2012Feb"; + + if (typeof JSIO !== "object") { JSIO = {}; } + if ((typeof JSIO.version !== "string")) { + JSIO.version = version; + } + else if ((JSIO.version.length < 3) || + (JSIO.version.substring(0,3) !== "2.0")) { + JSIO.version += " " + version; + } + + // ======================================================= + // the base object, used as the prototype of all ByteReader objects. + var _byteReaderBase = function () { + this.position = 0; + // position must be incremented in .readByte() for all derived classes + }; + + _byteReaderBase.prototype._throwError = JSIO.throwError; + + _byteReaderBase.prototype._limitCheck = function(len, startPos) { + var LOE = { + len: len, + pos: startPos, + end: startPos+len + }; + + if (len === 0) return {len:0, pos:0, end:0}; + if (len < 0) this._throwError("Invalid read length"); + if (!this.length) return {len:len, pos:this.position, end:len+this.position}; + if (!startPos >= 0) LOE.pos = this.position; + if (this.length <= LOE.pos) this._throwError("EOF reached"); + + LOE.end = LOE.pos+len; + if (this.length < LOE.end) LOE.end = LOE.pos+(LOE.len = this.length-this.position); + return LOE; + } + + JSIO.SeekOrigin = { + Begin : 0, + Current : 1, + End : 2, + SEEK_SET : 0, + SEEK_CUR : 1, + SEEK_END : 2 + }; + + _byteReaderBase.prototype.seek = function(offset, origin) { + switch (origin) { + case JSIO.SeekOrigin.Begin: + if (offset == this.position) return this.position; + if (!this.length) { + if (offset < this.position) this._throwError('Uni-directional stream cannot seek backwards', null, 'seek'); + else if (offset > this.position) return this.read(offset - this.position); // read will limit check + } + else { + if (this.length < offset) this._throwError('Cannot seek past reader length', null, 'seek'); + this.position = offset; + } + break; + case JSIO.SeekOrigin.Current: + return this.seek(this.position + offset, JSIO.SeekOrigin.Begin); + break; + case JSIO.SeekOrigin.End: + if (!this.length) this._throwError('Uni-directional stream has no known end length for seek', null, 'seek'); + return this.seek(this.length - 1 + offset, JSIO.SeekOrigin.Begin); + break; + default: + this._throwError('Invalid seek method', null, 'seek'); + break; + } + + return this.position; + }; + + _byteReaderBase.prototype.read = function(len, startPos) { + var LOE = this._limitCheck(len, startPos); + if (LOE.len === 0) return []; + if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin); + + var bytesRead = []; + + // Faster methods with an array or stream + if (this.array && this.array.subarray) bytesRead = this.array.subarray(LOE.pos, LOE.end); + else if (this.array && this.array.slice) bytesRead = this.array.slice(LOE.pos, LOE.end); + else if (this.stream) bytesRead = this.stream.read(LOE.len, LOE.pos); + else if (this.length) { // Random-access stream + for(var i=LOE.pos; i<LOE.end; i++) { bytesRead.push(this.readByteAt(i)); } + } + else { // Uni-directional stream + for(var i=LOE.pos; i<LOE.end; i++) { + var b = this.readByte(); + if (b === null || b === undefined) break; + bytesRead.push(b); + } + } + this.position = LOE.end; + return bytesRead; + }; + + _byteReaderBase.prototype.beginRead = function(len, startPos, callback) { + var LOE = this._limitCheck(len, startPos); + if (LOE.len === 0) return setTimeout(function() { callback([]); }, 1); + if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin); + + var bytesRead = []; + var thisReader = this; + var leftToRead = LOE.len; + + var readBatchAsync = function() { + var c = 0; + var pos = thisReader.position; + + // read a 32k batch + var l = (leftToRead >= 32768) ? 32768 : leftToRead; + var newBytes = thisReader.read(l); + JSIO.massApply(bytesRead.push, bytesRead, newBytes); + c += l; + leftToRead -= l; + if (newBytes.length < l) leftToRead = 0; + + if (leftToRead>0) setTimeout(readBatchAsync, 1); + else callback(bytesRead); + }; + + // kickoff + setTimeout(readBatchAsync, 1); // always async, in ALL situations + return null; + }; + + _byteReaderBase.prototype.readToEnd = function() { + if (this.array && this.array.subarray) return this.array.subarray(this.position); + else if (this.array && this.array.slice) return this.array.slice(this.position); + else if (this.length) return this.read(this.length - this.position); + else return this.read(9000 * 9000); // over 9000 + }; + + _byteReaderBase.prototype.beginReadToEnd = function(callback) { + if (this.array && this.array.subarray) setTimeout(function() { callback( this.array.subarray(this.position) ); }, 1); + else if (this.array && this.array.slice) setTimeout(function() { callback( this.array.slice(this.position) ); }, 1); + else if (this.length) return this.beginRead(this.length - this.position, this.position, callback); + else return this.beginRead(9000 * 9000, this.position, callback); + }; + + // Generic routines; one of these two MUST be overloaded (preferrably both) + _byteReaderBase.prototype.readByte = function(){ + if (this.length && this.position >= this.length) return null; // EOF + + var oneByte; + if (this.array) oneByte = this.array[this.position++]; + else if (this.length) oneByte = this.readByteAt(this.position++); + else if (this.stream) oneByte = this.stream.read(1)[0]; + else oneByte = this.read(1)[0]; + return (oneByte === null || oneByte === undefined) ? null : oneByte; + }; + _byteReaderBase.prototype.readByteAt = function(i) { + var pos = this.position; // no position changes on this one + if (i === null || i === undefined) i = this.position; + + var oneByte; + if (this.array) oneByte = this.array[i]; + else if (i === pos) oneByte = this.readByte(); + else if (this.stream) oneByte = this.stream.read(1, i)[0]; + else oneByte = this.read(1, i)[0]; + + this.position = pos; + return (oneByte === null || oneByte === undefined) ? null : oneByte; + } + + _byteReaderBase.prototype.readBytes = _byteReaderBase.prototype.read; + _byteReaderBase.prototype.beginReadBytes = function(len, callback) { return this.beginRead(len, this.position, callback); }; + + _byteReaderBase.prototype.readNumber = function(len, startPos){ + var LOE = this._limitCheck(len, startPos); + if (LOE.len === 0) LOE.len = 1; + if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin); + + var result = 0; + var bytes = this.read(LOE.len, LOE.pos); + for (var i=bytes.length-1; i>=0; i--) { + // IE only supports 32-bit integer shifting + //result = result << 8 | bytes[i]; + result = result*256 + bytes[i]; + } + return result; + }; + + _byteReaderBase.prototype.readString = function(len, startPos){ + var LOE = this._limitCheck(len, startPos); + if (LOE.len === 0) return ''; + if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin); + + var result = ''; + var bytes = this.read(LOE.len, LOE.pos); + for(var i=0; i<bytes.length; i++){ + result += String.fromCharCode(bytes[i]); + } + return result; + }; + + _byteReaderBase.prototype.readNullTerminatedString = function(startPos){ + var pos = startPos || this.position; + if (this.length && this.length < pos) this._throwError('EOF reached', null, 'readNullTerminatedString'); + if (pos != this.position) this.seek(pos, JSIO.SeekOrigin.Begin); + + var slarge = ""; + var s = ""; + var c = 0; + + // Faster method with an array + if (this.array && this.array.indexOf) { + var len = pos - this.array.indexOf(0, pos); + if (len > 0) return this.readString(len, pos); + } + + var ch; + while(1) { + ch = String.fromCharCode(this.readByteAt(pos+c)); + if (ch === null) break; + + s += ch; + c++; + if(c >= 32768) { + slarge += s; + s = ""; + pos += c; + this.position += c; + c = 0; + } + }; + this.position = pos + c; + return slarge + s; + }; + + _byteReaderBase.prototype.beginReadNullTerminatedString = function(callback, startPos){ + var pos = startPos || this.position; + if (this.length && this.length < pos) this._throwError('EOF reached', null, 'beginReadNullTerminatedString'); + + var slarge = ""; + var s = ""; + var thisBinStream = this; + + var readBatchAsync = function() { + var c = 0; + + var ch; + while(1) { + ch = String.fromCharCode(this.readByteAt(pos+c)); + if (ch === null) break; + + s += ch; + c++; + if(c >= 32768) { + slarge += s; + s = ""; + pos += c; + this.position += c; + c = 0; + } + }; + + thisBinStream.position = pos + c; + if (ch!==null) setTimeout(readBatchAsync, 1); + else callback(slarge+s); + }; + + // Faster method with an array + if (this.array && this.array.indexOf) { + var len = pos - this.array.indexOf(0, pos); + if (len > 0) readBatchASync = function() { callback(thisBinStream.readString(len, pos)); }; + } + + // kickoff + setTimeout(readBatchAsync, 1); // always async, in ALL situations + return null; + }; + + + + JSIO._ByteReaderBase = _byteReaderBase; + // ======================================================= + + + + + // ======================================================= + // reads from an array of bytes. + // This basically wraps a readByte() fn onto array access. + var _arrayReader = function(array) { + if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.ArrayReader', 'ctor'); + this.position = 0; + this.array = array; + this.length = array.length; + this._typename = "JSIO.ArrayReader"; + this._version = version; + return this; + }; + + _arrayReader.prototype = new JSIO._ByteReaderBase(); + + _arrayReader.prototype.readByte = function() { + if (this.position >= this.array.length) return null; // EOF + return this.array[this.position++]; + }; + _arrayReader.prototype.readByteAt = function(i) { + return this.array[i]; + }; + + // ======================================================= + + + // ======================================================= + // reads bytes at a time from a defined segment of a stream. + var _streamSegmentReader = function(stream, offset, len) { + if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.StreamSegmentReader', 'ctor'); + if (!stream) this._throwError('You must pass a non-null stream', 'JSIO.StreamSegmentReader', 'ctor'); + + if (!(offset >= 1)) offset = 0; + if (!(len >= 1)) len = 0; + + this.stream = stream; + this.array = null; + if (stream.array) { + var end = len ? offset + len : null; + if (stream.array.subarray) this.array = stream.array.subarray(offset, end); + else if (stream.array.slice) this.array = stream.array.slice(offset, end); + } + this.length = this.array ? this.array.length : (stream.length ? stream.length - offset : null); + this.offset = offset; + this.limit = len; + this.position = 0; + this._typeName = 'JSIO.StreamSegmentReader'; + this._version = version; + + if (this.array) { + this.readByte = _arrayReader.prototype.readByte; + this.readByteAt = _arrayReader.prototype.readByteAt; + } + return this; + }; + + _streamSegmentReader.prototype = new JSIO._ByteReaderBase(); + + _streamSegmentReader.prototype.readByte = function() { + if (this.limit && this.position >= this.limit) return null; // EOF + this.position++; + return this.stream.readByteAt(this.offset + this.position - 1); + }; + _streamSegmentReader.prototype.readByteAt = function(i) { + if (this.limit && i >= this.limit) return null; // EOF + return this.stream.readByteAt(this.offset + i); + }; + + // ======================================================= + + JSIO.ArrayReader = _arrayReader; + JSIO.StreamReader = _streamSegmentReader; + JSIO.StreamSegmentReader = _streamSegmentReader; })(); @@ -574,195 +574,195 @@ // This work is licensed under the GPLv3. (function(){ - var version = "2.0 2012Feb"; - var typename = "JSIO.BinaryUrlStream"; - - if ((typeof JSIO !== "object") || - (typeof JSIO.version !== "string") || - (JSIO.version.length < 3) || - (JSIO.version.substring(0,3) !== "2.0")) - JSIO.throwError('This extension requires JSIO.core.js v2.0', typename); - - if (typeof JSIO._ByteReaderBase !== "function") - JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename); - - if (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) { - var IEBinaryToArray_ByteStr_Script = - "<!-- IEBinaryToArray_ByteStr -->\r\n"+ - "<script type='text/vbscript'>\r\n"+ - "Function IEBinaryToArray_ByteStr(Binary)\r\n"+ - " IEBinaryToArray_ByteStr = CStr(Binary)\r\n"+ - "End Function\r\n"+ - "Function IEBinaryToArray_ByteAsc_Last(Binary)\r\n"+ - " Dim lastIndex\r\n"+ - " lastIndex = LenB(Binary)\r\n"+ - " if lastIndex mod 2 Then\r\n"+ - " IEBinaryToArray_ByteAsc_Last = AscB( MidB( Binary, lastIndex, 1 ) )\r\n"+ - " Else\r\n"+ - " IEBinaryToArray_ByteAsc_Last = -1\r\n"+ - " End If\r\n"+ - "End Function\r\n"+ - "</script>\r\n"; - - // inject VBScript - document.write(IEBinaryToArray_ByteStr_Script); - } - - JSIO.IEByteMapping = null; - - var bus = function(url, callback) { - if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.BinaryUrlStream', 'ctor'); - - this.callback = callback; - this.position = 0; - this.length = null; - this.readByte = JSIO.ArrayReader.prototype.readByte; - this.readByteAt = JSIO.ArrayReader.prototype.readByteAt; - this.req = null; - this._typename = typename; - this._version = version; - this.status = "-none-"; - - var _IeGetBinResource = function(fileURL){ - var binStream = this; - // http://stackoverflow.com/questions/1919972/how-do-i-access-xhr-responsebody-for-binary-data-from-javascript-in-ie - - // my helper to convert from responseBody to a byte array - var convertResponseBodyToArray = function (binary) { - var byteArray = new Array; - - try { - // very fast; very little work involved - byteArray = new VBArray(binary).toArray(); - } - catch(err) { - // use the BinaryToArray VBScript - if (!JSIO.IEByteMapping) { - JSIO.IEByteMapping = {}; - for ( var i = 0; i < 256; i++ ) { - for ( var j = 0; j < 256; j++ ) { - JSIO.IEByteMapping[ String.fromCharCode( i + j * 256 ) ] = [ i, j ]; - } - } - } - var rawBytes = IEBinaryToArray_ByteStr(binary); - var lastAsc = IEBinaryToArray_ByteAsc_Last(binary); - - for ( var i = 0; i < rawBytes.length; i++ ) { - byteArray.push.apply(byteArray, JSIO.IEByteMapping[ rawBytes.substr(i,1) ]); - } - if (lastAsc >= 0) byteArray.push(lastAsc); - } - - return byteArray; - }; - - this.req = (function() { - if (window.XMLHttpRequest) return new window.XMLHttpRequest(); - else if (window.ActiveXObject) { - // the many versions of IE's XML fetchers - var AXOs = [ - 'MSXML2.XMLHTTP.6.0', - 'MSXML2.XMLHTTP.5.0', - 'MSXML2.XMLHTTP.4.0', - 'MSXML2.XMLHTTP.3.0', - 'MSXML2.XMLHTTP', - 'Microsoft.XMLHTTP', - 'MSXML.XMLHTTP' - ]; - for (var i = 0; i < AXOs.length; i++) { - try { return new ActiveXObject(AXOs[i]); } - catch(e) { continue; } - } - } - return null; - })(); - this.req.open("GET", fileURL, true); - this.req.setRequestHeader("Accept-Charset", "x-user-defined"); - this.req.onreadystatechange = function(event){ - if (binStream.req.readyState == 4) { - binStream.status = "Status: " + binStream.req.status + ' ' + binStream.req.statusText; - if (binStream.req.status == 200) { - binStream.array = convertResponseBodyToArray(binStream.req.responseBody); - binStream.length = binStream.array.length; - if (binStream.length < 0) this._throwError('Failed to load "'+ fileURL + '" after converting'); - - if (typeof binStream.callback == "function") binStream.callback(binStream); - } - else { - binStream._throwError('Failed to load "'+ fileURL + '": HTTP ' + binStream.status); - } - } - }; - this.req.send(); - }; - - var _NormalGetBinResource = function(fileURL){ - var binStream= this; - this.req = new XMLHttpRequest(); - this.req.open('GET', fileURL, true); - this.req.onreadystatechange = function(aEvt) { - if (binStream.req.readyState == 4) { - binStream.status = "Status: " + binStream.req.status + ' ' + binStream.req.statusText; - if(binStream.req.status == 200){ - var fileContents = binStream.req.responseText; - binStream.length = fileContents.byteLength; - binStream.array = fileContents.split(''); - for ( var i = 0; i < binStream.array.length; i++ ) { - binStream.array[i] = binStream.array[i].charCodeAt(0) & 0xff; - } - - if (typeof binStream.callback == "function") binStream.callback(binStream); - } - else { - binStream._throwError('Failed to load "'+ fileURL + '": HTTP ' + binStream.status); - } - } - }; - //XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com] - this.req.overrideMimeType('text/plain; charset=x-user-defined'); - this.req.send(null); - }; - - // http://stackoverflow.com/questions/327685/is-there-a-way-to-read-binary-data-into-javascript - var _ArrayBufferGetBinResource = function(fileURL){ - var binStream= this; - this.req = new XMLHttpRequest(); - this.req.open('GET', fileURL, true); - this.req.onreadystatechange = function(aEvt) { - if (binStream.req.readyState == 4) { - binStream.status = "Status: " + binStream.req.status + ' ' + binStream.req.statusText; - if(binStream.req.status == 200){ - var fileContents = binStream.req.response; - binStream.length = fileContents.byteLength; - binStream.array = new Uint8Array(fileContents); - - if (typeof binStream.callback == "function") binStream.callback(binStream); - } - else { - binStream._throwError('Failed to load "'+ fileURL + '": HTTP ' + binStream.status); - } - } - }; - this.req.responseType = 'arraybuffer'; - this.req.overrideMimeType('text/plain; charset=x-user-defined'); - this.req.send(null); - }; - - - if (typeof ArrayBuffer !== 'undefined') _ArrayBufferGetBinResource.apply(this, [url]); - else if (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) _IeGetBinResource.apply(this, [url]); - else _NormalGetBinResource.apply(this, [url]); - }; - - bus.prototype = new JSIO._ByteReaderBase(); - - bus.prototype.readByte = function(){ - var byte = this.readByteAt(this.position++); - return (byte === null || byte === undefined) ? null : byte; - }; - - JSIO.BinaryUrlStream = bus; + var version = "2.0 2012Feb"; + var typename = "JSIO.BinaryUrlStream"; + + if ((typeof JSIO !== "object") || + (typeof JSIO.version !== "string") || + (JSIO.version.length < 3) || + (JSIO.version.substring(0,3) !== "2.0")) + JSIO.throwError('This extension requires JSIO.core.js v2.0', typename); + + if (typeof JSIO._ByteReaderBase !== "function") + JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename); + + if (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) { + var IEBinaryToArray_ByteStr_Script = + "<!-- IEBinaryToArray_ByteStr -->\r\n"+ + "<script type='text/vbscript'>\r\n"+ + "Function IEBinaryToArray_ByteStr(Binary)\r\n"+ + " IEBinaryToArray_ByteStr = CStr(Binary)\r\n"+ + "End Function\r\n"+ + "Function IEBinaryToArray_ByteAsc_Last(Binary)\r\n"+ + " Dim lastIndex\r\n"+ + " lastIndex = LenB(Binary)\r\n"+ + " if lastIndex mod 2 Then\r\n"+ + " IEBinaryToArray_ByteAsc_Last = AscB( MidB( Binary, lastIndex, 1 ) )\r\n"+ + " Else\r\n"+ + " IEBinaryToArray_ByteAsc_Last = -1\r\n"+ + " End If\r\n"+ + "End Function\r\n"+ + "</script>\r\n"; + + // inject VBScript + document.write(IEBinaryToArray_ByteStr_Script); + } + + JSIO.IEByteMapping = null; + + var bus = function(url, callback) { + if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.BinaryUrlStream', 'ctor'); + + this.callback = callback; + this.position = 0; + this.length = null; + this.readByte = JSIO.ArrayReader.prototype.readByte; + this.readByteAt = JSIO.ArrayReader.prototype.readByteAt; + this.req = null; + this._typename = typename; + this._version = version; + this.status = "-none-"; + + var _IeGetBinResource = function(fileURL){ + var binStream = this; + // http://stackoverflow.com/questions/1919972/how-do-i-access-xhr-responsebody-for-binary-data-from-javascript-in-ie + + // my helper to convert from responseBody to a byte array + var convertResponseBodyToArray = function (binary) { + var byteArray = new Array; + + try { + // very fast; very little work involved + byteArray = new VBArray(binary).toArray(); + } + catch(err) { + // use the BinaryToArray VBScript + if (!JSIO.IEByteMapping) { + JSIO.IEByteMapping = {}; + for ( var i = 0; i < 256; i++ ) { + for ( var j = 0; j < 256; j++ ) { + JSIO.IEByteMapping[ String.fromCharCode( i + j * 256 ) ] = [ i, j ]; + } + } + } + var rawBytes = IEBinaryToArray_ByteStr(binary); + var lastAsc = IEBinaryToArray_ByteAsc_Last(binary); + + for ( var i = 0; i < rawBytes.length; i++ ) { + byteArray.push.apply(byteArray, JSIO.IEByteMapping[ rawBytes.substr(i,1) ]); + } + if (lastAsc >= 0) byteArray.push(lastAsc); + } + + return byteArray; + }; + + this.req = (function() { + if (window.XMLHttpRequest) return new window.XMLHttpRequest(); + else if (window.ActiveXObject) { + // the many versions of IE's XML fetchers + var AXOs = [ + 'MSXML2.XMLHTTP.6.0', + 'MSXML2.XMLHTTP.5.0', + 'MSXML2.XMLHTTP.4.0', + 'MSXML2.XMLHTTP.3.0', + 'MSXML2.XMLHTTP', + 'Microsoft.XMLHTTP', + 'MSXML.XMLHTTP' + ]; + for (var i = 0; i < AXOs.length; i++) { + try { return new ActiveXObject(AXOs[i]); } + catch(e) { continue; } + } + } + return null; + })(); + this.req.open("GET", fileURL, true); + this.req.setRequestHeader("Accept-Charset", "x-user-defined"); + this.req.onreadystatechange = function(event){ + if (binStream.req.readyState == 4) { + binStream.status = "Status: " + binStream.req.status + ' ' + binStream.req.statusText; + if (binStream.req.status == 200) { + binStream.array = convertResponseBodyToArray(binStream.req.responseBody); + binStream.length = binStream.array.length; + if (binStream.length < 0) this._throwError('Failed to load "'+ fileURL + '" after converting'); + + if (typeof binStream.callback == "function") binStream.callback(binStream); + } + else { + binStream._throwError('Failed to load "'+ fileURL + '": HTTP ' + binStream.status); + } + } + }; + this.req.send(); + }; + + var _NormalGetBinResource = function(fileURL){ + var binStream= this; + this.req = new XMLHttpRequest(); + this.req.open('GET', fileURL, true); + this.req.onreadystatechange = function(aEvt) { + if (binStream.req.readyState == 4) { + binStream.status = "Status: " + binStream.req.status + ' ' + binStream.req.statusText; + if(binStream.req.status == 200){ + var fileContents = binStream.req.responseText; + binStream.length = fileContents.byteLength; + binStream.array = fileContents.split(''); + for ( var i = 0; i < binStream.array.length; i++ ) { + binStream.array[i] = binStream.array[i].charCodeAt(0) & 0xff; + } + + if (typeof binStream.callback == "function") binStream.callback(binStream); + } + else { + binStream._throwError('Failed to load "'+ fileURL + '": HTTP ' + binStream.status); + } + } + }; + //XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com] + if (!!this.req.overrideMimeType) this.req.overrideMimeType('text/plain; charset=x-user-defined'); + this.req.send(null); + }; + + // http://stackoverflow.com/questions/327685/is-there-a-way-to-read-binary-data-into-javascript + var _ArrayBufferGetBinResource = function(fileURL){ + var binStream= this; + this.req = new XMLHttpRequest(); + this.req.open('GET', fileURL, true); + this.req.onreadystatechange = function(aEvt) { + if (binStream.req.readyState == 4) { + binStream.status = "Status: " + binStream.req.status + ' ' + binStream.req.statusText; + if(binStream.req.status == 200){ + var fileContents = binStream.req.response; + binStream.length = fileContents.byteLength; + binStream.array = new Uint8Array(fileContents); + if (typeof binStream.callback == "function") binStream.callback(binStream); + } + else { + binStream._throwError('Failed to load "'+ fileURL + '": HTTP ' + binStream.status); + } + } + }; + this.req.responseType = 'arraybuffer'; + // http://stackoverflow.com/questions/11284728/how-do-i-access-8-bit-binary-data-from-javascript-in-opera + if (!!this.req.overrideMimeType) this.req.overrideMimeType('application/octet-stream; charset=x-user-defined'); + this.req.send(null); + }; + + + if (typeof ArrayBuffer !== 'undefined') _ArrayBufferGetBinResource.apply(this, [url]); + else if (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) _IeGetBinResource.apply(this, [url]); + else _NormalGetBinResource.apply(this, [url]); + }; + + bus.prototype = new JSIO._ByteReaderBase(); + + bus.prototype.readByte = function(){ + var oneByte = this.readByteAt(this.position++); + return (oneByte === null || oneByte === undefined) ? null : oneByte; + }; + + JSIO.BinaryUrlStream = bus; })(); @@ -786,147 +786,147 @@ // This work is licensed under the GPLv3. (function(){ - var version = "2.0 2012Feb"; - var typename = "JSIO.TextDecoder"; - - if ((typeof JSIO !== "object") || - (typeof JSIO.version !== "string") || - (JSIO.version.length < 3) || - (JSIO.version.substring(0,3) !== "2.0")) - JSIO.throwError('This extension requires JSIO.core.js v2.0', typename); - - if (typeof JSIO._ByteReaderBase !== "function") - JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename); - - var _ansi = function(reader) { - if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.TextDecoder.ANSI', 'ctor'); - this.byteReader = reader; - this.charWidth = 1; - this._version = version; - this._typename = typename + ".ANSI"; - return this; - }; - - _ansi.prototype.readChar = function() { - var code = this.byteReader.readByte(); - return (code < 0) ? null : String.fromCharCode(code); - }; - - _ansi.prototype.parseChar = function(code) { - return (code < 0) ? null : String.fromCharCode(code); - }; - - var _utf16 = function(reader) { - if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.TextDecoder.UTF16', 'ctor'); - this.byteReader = reader; - this.charWidth = 2; - this.bomState = 0; - this._version = version; - this._typename = typename + ".UTF16"; - return this; - }; - - _utf16.prototype.readChar = function() { - var b1 = this.byteReader.readByte(); - if (b1 < 0) return null; - var b2 = this.byteReader.readByte(); - if (b2 < 0) this._throwError('Incomplete UTF16 character', null, 'readChar'); - - if ((this.bomState === 0) && ((b1 + b2) == 509)) { - this.bomState = (b2 == 254) ? 1 : 2; - - b1 = this.byteReader.readByte(); - if (b1 < 0) return null; - b2 = this.byteReader.readByte(); - if (b2 < 0) this._throwError('Incomplete UTF16 character', null, 'readChar'); - } - else { - this.bomState = 1; - } - return this.parseChar(b1, b2); - }; - - _utf16.prototype.parseChar = function(b1, b2) { - return String.fromCharCode( this.bomState == 1 ? (b2 << 8 | b1) : (b1 << 8 | b2) ); - } - - /* RFC 3629 */ - var _utf8 = function(reader) { - if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.TextDecoder.UTF8', 'ctor'); - this.byteReader = reader; - this.charWidth = null; - this.waitBom = true; - this.strict = false; - this.pendingChar = null; - this._version = version; - this._typename = typename + ".UTF8"; - return this; - }; - - _utf8.prototype.readChar = function() { - var ch = null; - do { - if (this.pendingChar !== null) { - ch = this.pendingChar; - this.pendingChar = null; - } - else { - var b1 = this.byteReader.readByte(); - if (b1 === null) return null; - - if ((b1 & 0x80) === 0) ch = String.fromCharCode(b1); - else { - var currentPrefix = 0xC0; - var ttlBytes = 0; - do { - var mask = currentPrefix >> 1 | 0x80; - if((b1 & mask) == currentPrefix) break; - currentPrefix = currentPrefix >> 1 | 0x80; - } while(++ttlBytes < 5); - - if (ttlBytes > 0) { - var code; - if (ttlBytes === 1) code = (b1 & 0x1F) << 6 | (this.byteReader.readByte() & 0x3F); - else { - code = code << 6*ttlBytes - var bytes = this.byteReader.read(ttlBytes); - for (var i = 0; i > ttlBytes; i++) { - var bi = bytes[i]; - if ((bi & 0xC0) != 0x80) this._throwError('Invalid sequence character', null, 'readChar'); - code = (code << 6) | (bi & 0x3F); - } - } - - if (code <= 0xFFFF) { - ch = (code == 0xFEFF && this.waitBom) ? null : String.fromCharCode(code); - } - else { - var v = code - 0x10000; - var w1 = 0xD800 | ((v >> 10) & 0x3FF); - var w2 = 0xDC00 | (v & 0x3FF); - this.pendingChar = String.fromCharCode(w2); - ch = String.fromCharCode(w1); - } - } - else { - // a byte higher than 0x80. - if (this.strict) this._throwError('Invalid character', null, 'readChar'); - // fall back to "super ascii" (eg IBM-437) - else ch = String.fromCharCode(b1); - } - } - } - this.waitBom = false; - } while(ch === null); - return ch; - }; - - JSIO.TextDecoder = { - Default : _ansi, - ANSI : _ansi, - UTF16 : _utf16, - UTF8 : _utf8 - }; + var version = "2.0 2012Feb"; + var typename = "JSIO.TextDecoder"; + + if ((typeof JSIO !== "object") || + (typeof JSIO.version !== "string") || + (JSIO.version.length < 3) || + (JSIO.version.substring(0,3) !== "2.0")) + JSIO.throwError('This extension requires JSIO.core.js v2.0', typename); + + if (typeof JSIO._ByteReaderBase !== "function") + JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename); + + var _ansi = function(reader) { + if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.TextDecoder.ANSI', 'ctor'); + this.byteReader = reader; + this.charWidth = 1; + this._version = version; + this._typename = typename + ".ANSI"; + return this; + }; + + _ansi.prototype.readChar = function() { + var code = this.byteReader.readByte(); + return (code < 0) ? null : String.fromCharCode(code); + }; + + _ansi.prototype.parseChar = function(code) { + return (code < 0) ? null : String.fromCharCode(code); + }; + + var _utf16 = function(reader) { + if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.TextDecoder.UTF16', 'ctor'); + this.byteReader = reader; + this.charWidth = 2; + this.bomState = 0; + this._version = version; + this._typename = typename + ".UTF16"; + return this; + }; + + _utf16.prototype.readChar = function() { + var b1 = this.byteReader.readByte(); + if (b1 < 0) return null; + var b2 = this.byteReader.readByte(); + if (b2 < 0) this._throwError('Incomplete UTF16 character', null, 'readChar'); + + if ((this.bomState === 0) && ((b1 + b2) == 509)) { + this.bomState = (b2 == 254) ? 1 : 2; + + b1 = this.byteReader.readByte(); + if (b1 < 0) return null; + b2 = this.byteReader.readByte(); + if (b2 < 0) this._throwError('Incomplete UTF16 character', null, 'readChar'); + } + else { + this.bomState = 1; + } + return this.parseChar(b1, b2); + }; + + _utf16.prototype.parseChar = function(b1, b2) { + return String.fromCharCode( this.bomState == 1 ? (b2 << 8 | b1) : (b1 << 8 | b2) ); + } + + /* RFC 3629 */ + var _utf8 = function(reader) { + if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.TextDecoder.UTF8', 'ctor'); + this.byteReader = reader; + this.charWidth = null; + this.waitBom = true; + this.strict = false; + this.pendingChar = null; + this._version = version; + this._typename = typename + ".UTF8"; + return this; + }; + + _utf8.prototype.readChar = function() { + var ch = null; + do { + if (this.pendingChar !== null) { + ch = this.pendingChar; + this.pendingChar = null; + } + else { + var b1 = this.byteReader.readByte(); + if (b1 === null) return null; + + if ((b1 & 0x80) === 0) ch = String.fromCharCode(b1); + else { + var currentPrefix = 0xC0; + var ttlBytes = 0; + do { + var mask = currentPrefix >> 1 | 0x80; + if((b1 & mask) == currentPrefix) break; + currentPrefix = currentPrefix >> 1 | 0x80; + } while(++ttlBytes < 5); + + if (ttlBytes > 0) { + var code; + if (ttlBytes === 1) code = (b1 & 0x1F) << 6 | (this.byteReader.readByte() & 0x3F); + else { + code = code << 6*ttlBytes + var bytes = this.byteReader.read(ttlBytes); + for (var i = 0; i > ttlBytes; i++) { + var bi = bytes[i]; + if ((bi & 0xC0) != 0x80) this._throwError('Invalid sequence character', null, 'readChar'); + code = (code << 6) | (bi & 0x3F); + } + } + + if (code <= 0xFFFF) { + ch = (code == 0xFEFF && this.waitBom) ? null : String.fromCharCode(code); + } + else { + var v = code - 0x10000; + var w1 = 0xD800 | ((v >> 10) & 0x3FF); + var w2 = 0xDC00 | (v & 0x3FF); + this.pendingChar = String.fromCharCode(w2); + ch = String.fromCharCode(w1); + } + } + else { + // a byte higher than 0x80. + if (this.strict) this._throwError('Invalid character', null, 'readChar'); + // fall back to "super ascii" (eg IBM-437) + else ch = String.fromCharCode(b1); + } + } + } + this.waitBom = false; + } while(ch === null); + return ch; + }; + + JSIO.TextDecoder = { + Default : _ansi, + ANSI : _ansi, + UTF16 : _utf16, + UTF8 : _utf8 + }; })(); @@ -962,122 +962,122 @@ (function(){ - var version = "2.0 2012Feb"; - var typename = "JSIO.TextReader"; - - if (typeof JSIO._ByteReaderBase !== "function") - JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename); - - var tr = function(textDecoder) { - if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename, 'ctor'); - this.decoder = textDecoder; - this._version = version; - this._typename = typename; - this.unreads = []; - }; - - // read one char - tr.prototype.readChar = function() { - return (this.unreads.length > 0) ? this.unreads.pop() : this.decoder.readChar(); - }; - - // read a length of data - tr.prototype.read = function(n) { - // ANSI makes this easy - if (this.decoder.charWidth === 1) return JSIO.massApply(String.fromCharCode, new String, this.decoder.byteReader.read(n), true); - - var s = ""; - for (vari=0; i<n; i++) { - var ch = this.readChar(); - if (ch !== null) s+= ch; - else break; - } - return s; - }; - - tr.prototype.unreadChar = function(ch) { - this.unreads.push(ch); - }; - - tr.prototype.readToEnd = function() { - // ANSI makes this easy - if (this.decoder.charWidth === 1) return JSIO.massApply(String.fromCharCode, new String, this.decoder.byteReader.readToEnd(n), true); - - var slarge = ""; - var s = ""; - var c = 0; - var ch = this.readChar(); - while(ch !== null) { - s += ch; - c++; - if(c >= 32768) { - slarge += s; - s = ""; - c = 0; - } - ch = this.readChar(); - } - return slarge + s; - }; - - tr.prototype.beginReadToEnd = function(callback) { - // ANSI makes this easy - if (this.decoder.charWidth === 1) { - this.decoder.byteReader.beginReadToEnd(function (bytes) { - callback( JSIO.massApply(String.fromCharCode, new String, bytes, true) ); - }); - return null; - } - - var slarge = ""; - var s = ""; - var txtrdr = this; - - var readBatchAsync = function() { - var c = 0; - var ch = txtrdr.readChar(); - while(ch !== null) { - s += ch;c++; - if(c >= 32768) { - slarge += s; - s = ""; - break; - } - ch = txtrdr.readChar(); - } - if (ch!==null){ - setTimeout(readBatchAsync, 1); - } - else { - callback(slarge+s); - } - }; - - // kickoff - setTimeout(readBatchAsync, 1); // always async, in ALL situations - return null; - }; - - tr.prototype.readLine = function() { - var s = ""; - var ch = this.readChar(); - if (ch === null) return null; - - while(ch != "\r" && ch != "\n") { - s += ch; - ch = this.readChar(); - if (ch === null) return s; - } - if(ch == "\r") { - ch = this.readChar(); - if(ch !== null && ch != "\n"){ - this.unreadChar(ch); - } - } - return s; - }; - - JSIO.TextReader = tr; + var version = "2.0 2012Feb"; + var typename = "JSIO.TextReader"; + + if (typeof JSIO._ByteReaderBase !== "function") + JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename); + + var tr = function(textDecoder) { + if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename, 'ctor'); + this.decoder = textDecoder; + this._version = version; + this._typename = typename; + this.unreads = []; + }; + + // read one char + tr.prototype.readChar = function() { + return (this.unreads.length > 0) ? this.unreads.pop() : this.decoder.readChar(); + }; + + // read a length of data + tr.prototype.read = function(n) { + // ANSI makes this easy + if (this.decoder.charWidth === 1) return JSIO.massApply(String.fromCharCode, new String, this.decoder.byteReader.read(n), true); + + var s = ""; + for (vari=0; i<n; i++) { + var ch = this.readChar(); + if (ch !== null) s+= ch; + else break; + } + return s; + }; + + tr.prototype.unreadChar = function(ch) { + this.unreads.push(ch); + }; + + tr.prototype.readToEnd = function() { + // ANSI makes this easy + if (this.decoder.charWidth === 1) return JSIO.massApply(String.fromCharCode, new String, this.decoder.byteReader.readToEnd(n), true); + + var slarge = ""; + var s = ""; + var c = 0; + var ch = this.readChar(); + while(ch !== null) { + s += ch; + c++; + if(c >= 32768) { + slarge += s; + s = ""; + c = 0; + } + ch = this.readChar(); + } + return slarge + s; + }; + + tr.prototype.beginReadToEnd = function(callback) { + // ANSI makes this easy + if (this.decoder.charWidth === 1) { + this.decoder.byteReader.beginReadToEnd(function (bytes) { + callback( JSIO.massApply(String.fromCharCode, new String, bytes, true) ); + }); + return null; + } + + var slarge = ""; + var s = ""; + var txtrdr = this; + + var readBatchAsync = function() { + var c = 0; + var ch = txtrdr.readChar(); + while(ch !== null) { + s += ch;c++; + if(c >= 32768) { + slarge += s; + s = ""; + break; + } + ch = txtrdr.readChar(); + } + if (ch!==null){ + setTimeout(readBatchAsync, 1); + } + else { + callback(slarge+s); + } + }; + + // kickoff + setTimeout(readBatchAsync, 1); // always async, in ALL situations + return null; + }; + + tr.prototype.readLine = function() { + var s = ""; + var ch = this.readChar(); + if (ch === null) return null; + + while(ch != "\r" && ch != "\n") { + s += ch; + ch = this.readChar(); + if (ch === null) return s; + } + if(ch == "\r") { + ch = this.readChar(); + if(ch !== null && ch != "\n"){ + this.unreadChar(ch); + } + } + return s; + }; + + JSIO.TextReader = tr; })(); @@ -1116,111 +1116,111 @@ // This work is licensed under the GPLv3. (function(){ - var version = "2.0 2012Feb"; - var typename = "JSIO.Crc32"; - - if (typeof JSIO._ByteReaderBase !== "function") - JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename); - - JSIO.crc32Table = null; - JSIO.crc32Polynomial = 0xEDB88320; - - var crc32TableCalc = function () { - // do this once only, for all instances - if (JSIO.crc32Table) return; - JSIO.crc32Table = new Array(256); - for (var i = 0; i < 256; i++) { - var c=i; - for (var k = 0; k < 8; k++) { - if ((c & 1) == 1) c = JSIO.crc32Polynomial ^ (c>>>1); - else c >>>= 1; - } - JSIO.crc32Table[i] = c; - } - }; - - JSIO.computeCrc32 = function(str) { - crc32TableCalc(); // once - var c = 0xFFFFFFFF; - var sL = str.length; - if (typeof str == "object") { - for (var n1=0; n1<sL; n1++) { - c = JSIO.crc32Table[(c&0xff) ^ str[n1]] ^ (c>>>8); - } - } else { - for (var n2=0; n2<sL; n2++) { - c = JSIO.crc32Table[(c&0xff) ^ str.charCodeAt(n2)] ^ (c>>>8); - } - } - c ^= 0xFFFFFFFF; - if (c < 0) c += 0xFFFFFFFF+1; - return c; - }; - - // ======================================================= - var _crc32 = function() { - if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename, 'ctor'); - crc32TableCalc(); // once - this._typename = typename; - this._version = version; - this._runningCrc32 = 0xFFFFFFFF; - }; - - _crc32.prototype.slurpByte = function(b) { - var r = this._runningCrc32; - this._runningCrc32 = r>>>8 ^ JSIO.crc32Table[b ^ (r & 0x000000FF)]; - }; - - _crc32.prototype.result = function() { - var c = this._runningCrc32 ^ 0xFFFFFFFF; - if (c < 0) c += 0xFFFFFFFF+1; - return c; - }; - // ======================================================= - - - - var _crc32CalculatingReader = function(reader) { - if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.Crc32Reader', 'ctor'); - this._byteReader = reader; - this._typename = "JSIO.Crc32Reader"; - this._version = version; - this._crc32 = new JSIO.Crc32(); - }; - - _crc32CalculatingReader.prototype = new JSIO._ByteReaderBase(); - - _crc32CalculatingReader.prototype.readByte = function() { - var b = this._byteReader.readByte(); - if (b !== null) this._crc32.slurpByte(b); - this.position++; - return b; - }; - - _crc32CalculatingReader.prototype.read = function(len) { - if (len === 0) return []; - var bytes = this._byteReader.read(len); - len = bytes.length; - - var tbl = JSIO.crc32Table; - var r = this._crc32._runningCrc32; - var t; - for (var i = 0; i < len; i++) { - t = tbl[ bytes[i] ^ (r & 0x000000FF) ]; - r = (r >>> 8) ^ t; - } - this._crc32._runningCrc32 = r; - - this.position += len; - return bytes; - } - - _crc32CalculatingReader.prototype.crc32 = function() { - return this._crc32.result(); - }; - - JSIO.Crc32 = _crc32; - JSIO.Crc32Reader = _crc32CalculatingReader; + var version = "2.0 2012Feb"; + var typename = "JSIO.Crc32"; + + if (typeof JSIO._ByteReaderBase !== "function") + JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename); + + JSIO.crc32Table = null; + JSIO.crc32Polynomial = 0xEDB88320; + + var crc32TableCalc = function () { + // do this once only, for all instances + if (JSIO.crc32Table) return; + JSIO.crc32Table = new Array(256); + for (var i = 0; i < 256; i++) { + var c=i; + for (var k = 0; k < 8; k++) { + if ((c & 1) == 1) c = JSIO.crc32Polynomial ^ (c>>>1); + else c >>>= 1; + } + JSIO.crc32Table[i] = c; + } + }; + + JSIO.computeCrc32 = function(str) { + crc32TableCalc(); // once + var c = 0xFFFFFFFF; + var sL = str.length; + if (typeof str == "object") { + for (var n1=0; n1<sL; n1++) { + c = JSIO.crc32Table[(c&0xff) ^ str[n1]] ^ (c>>>8); + } + } else { + for (var n2=0; n2<sL; n2++) { + c = JSIO.crc32Table[(c&0xff) ^ str.charCodeAt(n2)] ^ (c>>>8); + } + } + c ^= 0xFFFFFFFF; + if (c < 0) c += 0xFFFFFFFF+1; + return c; + }; + + // ======================================================= + var _crc32 = function() { + if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename, 'ctor'); + crc32TableCalc(); // once + this._typename = typename; + this._version = version; + this._runningCrc32 = 0xFFFFFFFF; + }; + + _crc32.prototype.slurpByte = function(b) { + var r = this._runningCrc32; + this._runningCrc32 = r>>>8 ^ JSIO.crc32Table[b ^ (r & 0x000000FF)]; + }; + + _crc32.prototype.result = function() { + var c = this._runningCrc32 ^ 0xFFFFFFFF; + if (c < 0) c += 0xFFFFFFFF+1; + return c; + }; + // ======================================================= + + + + var _crc32CalculatingReader = function(reader) { + if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.Crc32Reader', 'ctor'); + this._byteReader = reader; + this._typename = "JSIO.Crc32Reader"; + this._version = version; + this._crc32 = new JSIO.Crc32(); + }; + + _crc32CalculatingReader.prototype = new JSIO._ByteReaderBase(); + + _crc32CalculatingReader.prototype.readByte = function() { + var b = this._byteReader.readByte(); + if (b !== null) this._crc32.slurpByte(b); + this.position++; + return b; + }; + + _crc32CalculatingReader.prototype.read = function(len) { + if (len === 0) return []; + var bytes = this._byteReader.read(len); + len = bytes.length; + + var tbl = JSIO.crc32Table; + var r = this._crc32._runningCrc32; + var t; + for (var i = 0; i < len; i++) { + t = tbl[ bytes[i] ^ (r & 0x000000FF) ]; + r = (r >>> 8) ^ t; + } + this._crc32._runningCrc32 = r; + + this.position += len; + return bytes; + } + + _crc32CalculatingReader.prototype.crc32 = function() { + return this._crc32.result(); + }; + + JSIO.Crc32 = _crc32; + JSIO.Crc32Reader = _crc32CalculatingReader; })(); @@ -1263,436 +1263,436 @@ (function(){ - var version = "2.0 2012Feb"; - var typename = "JSIO.InflatingReader"; - - if (typeof JSIO._ByteReaderBase !== "function") - JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename); - - // ======================================================= - // _InternalBitReader is used internally in the InflatingReader class. - // - - JSIO.bitShiftTable = null; - - var bitShiftTableCalc = function () { - // do this once only, for all instances - if (JSIO.bitShiftTable) return; - - var bits = 8; - JSIO.bitShiftTable = { - LSB: new Array(bits), - MSB: new Array(bits) - } - for (var bp = 0; bp < bits; bp++) { - var lim = bits - bp; - JSIO.bitShiftTable.LSB[bp] = new Array(lim); - JSIO.bitShiftTable.MSB[bp] = new Array(lim); - - var maskLSB = 1 << bp; - var maskMSB = 1 << lim-1; - for (var len = 1; len <= lim; len++) { - JSIO.bitShiftTable.LSB[bp][len-1] = maskLSB; - JSIO.bitShiftTable.MSB[bp][len-1] = maskMSB; - maskLSB |= 1 << bp+len; - maskMSB |= 1 << lim-len-1; - } - } - }; - - var _InternalBitReader = function(reader) { - if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename + '._InternalBitReader.ctor'); - this.bitsLength = 0; - this.bits = 0; - this.byteReader = reader; - this._typeName = typename + "._InternalBitReader"; - this._version = version; - bitShiftTableCalc(); - }; - - _InternalBitReader.prototype._throwError = JSIO.throwError; - - _InternalBitReader.prototype.readBit = function() { - if (this.bitsLength === 0) { - var nextByte = this.byteReader.readByte(); - if (nextByte === null) this._throwError('Unexpected end of stream', null, 'readBit'); - this.bits = nextByte; - this.bitsLength = 8; - } - - var bit = (this.bits & 1 << 8-this.bitsLength) !== 0; - this.bitsLength--; - return bit; - }; - - _InternalBitReader.prototype.align = function() { this.bitsLength = 0; }; - - _InternalBitReader.prototype.readBits = function(len, type) { - var data = 0; - type = type || 'LSB'; - var tbl = JSIO.bitShiftTable[type]; - var dl = 0; - while (len > 0) { - if (this.bitsLength === 0) { - var nextByte = this.byteReader.readByte(); - if (nextByte === null) this._throwError('Unexpected end of stream', null, 'read'+type); - this.bits = nextByte; - this.bitsLength = 8; - } - - var maskLen = (this.bitsLength >= len) ? len : this.bitsLength; - var bitsPos = 8-this.bitsLength; - data |= (this.bits & tbl[bitsPos][maskLen-1]) >>> bitsPos << dl; - - dl += maskLen; - len -= maskLen; - this.bitsLength -= maskLen; - }; - return data; - }; - - _InternalBitReader.prototype.readLSB = function(len) { return this.readBits(len, 'LSB'); } - _InternalBitReader.prototype.readMSB = function(len) { return this.readBits(len, 'MSB'); } - - // - // ======================================================= - - - /* inflating ByteReader - RFC 1951 */ - var _inflatingReader = function(reader) { - if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename, 'ctor'); - this._byteReader = reader; - this._bitReader = new _InternalBitReader(reader); - this._buffer = []; - this._bufferPosition = 0; - this._state = 0; - this._blockFinal = false; - this._typeName = typename; - this._version = version; - return this; - }; - - - // shared fns and variables - - var staticCodes = null; - var staticDistances = null; - - var clenMap = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]; - - var buildCodes = function(lengths){ - var i=0; - var codes = new Array(lengths.length); - var maxBits = lengths[0]; - for (i=1; i<lengths.length; i++) { - if (maxBits < lengths[i]) maxBits = lengths[i]; - } - - var bitLengthsCount = new Array(maxBits + 1); - for (i=0; i<=maxBits; i++) bitLengthsCount[i]=0; - - for (i=0; i<lengths.length; i++) { - ++bitLengthsCount[lengths[i]]; - } - - var nextCode = new Array(maxBits + 1); - var code = 0; - bitLengthsCount[0] = 0; - for (var bits=1; bits<=maxBits; bits++) { - code = (code + bitLengthsCount[bits - 1]) << 1; - nextCode[bits] = code; - } - - for (i=0; i<codes.length; i++) { - var len = lengths[i]; - if (len !== 0) { - codes[i] = nextCode[len]; - nextCode[len]++; - } - } - return codes; - }; - - var buildTree = function(codes, lengths){ - var nonEmptyCodes = []; - for(var i=0; i<codes.length; ++i) { - if(lengths[i] > 0) { - var code = {}; - code.bits = codes[i]; - code.length = lengths[i]; - code.index = i; - nonEmptyCodes.push(code); - } - } - return buildTreeBranch(nonEmptyCodes, 0, 0); - }; - - - var buildTreeBranch = function(codes, prefix, prefixLength){ - if (codes.length === 0) return null; - - var zeros = []; - var ones = []; - var branch = {}; - branch.isLeaf = false; - for(var i=0; i<codes.length; ++i) { - if (codes[i].length == prefixLength && codes[i].bits == prefix) { - branch.isLeaf = true; - branch.index = codes[i].index; - break; - } - else { - var nextBit = ((codes[i].bits >> (codes[i].length - prefixLength - 1)) & 1) > 0; - if (nextBit) ones.push(codes[i]); - else zeros.push(codes[i]); - } - } - if(!branch.isLeaf) { - branch.zero = buildTreeBranch(zeros, (prefix << 1), prefixLength + 1); - branch.one = buildTreeBranch(ones, (prefix << 1) | 1, prefixLength + 1); - } - return branch; - }; - - - var encodedLengthStart = [3,4,5,6,7,8,9,10, - 11,13,15,17,19,23,27,31,35,43,51,59,67,83,99, - 115,131,163,195,227,258]; - - var encodedLengthAdditionalBits = [0,0,0,0,0,0,0,0, - 1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0]; - - var encodedDistanceStart = [1,2,3,4, 5,7,9, 13,17,25, 33,49,65, - 97,129,193,257,385,513,769,1025,1537,2049, - 3073,4097,6145,8193,12289,16385,24577]; - - var encodedDistanceAdditionalBits = [0,0,0,0,1,1,2,2,3,3,4,4, - 5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13]; - - - var readDynamicTrees = function(bitReader){ - var hlit = bitReader.readLSB(5) + 257; - var hdist = bitReader.readLSB(5) + 1; - var hclen = bitReader.readLSB(4) + 4; - var clen = new Array(19); - var i; - for (i=0; i<clen.length; i++) { clen[i] = 0; } - for (i=0; i<hclen; i++) { clen[clenMap[i]] = bitReader.readLSB(3); } - - var clenCodes = buildCodes(clen); - var clenTree = buildTree(clenCodes, clen); - - var lengthsSequence = []; - while(lengthsSequence.length < hlit + hdist) { - var p = clenTree; - while(!p.isLeaf) { p = bitReader.readBit() ? p.one : p.zero; } - - var code = p.index; - if (code <= 15) lengthsSequence.push(code); - else if (code == 16) { - // TODO: replace with faster repeat algorythm - var repeat = bitReader.readLSB(2) + 3; - for(var q=0; q<repeat; ++q){ - lengthsSequence.push(lengthsSequence[lengthsSequence.length - 1]); - } - } - else if (code == 17) { - var repeat1 = bitReader.readLSB(3) + 3; - for(var q1=0; q1<repeat1; ++q1) { - lengthsSequence.push(0); - } - } - else if (code == 18) { - var repeat2 = bitReader.readLSB(7) + 11; - for(var q2=0; q2<repeat2; ++q2){ - lengthsSequence.push(0); - } - } - } - - var codesLengths = lengthsSequence.slice(0, hlit); - var codes = buildCodes(codesLengths); - var distancesLengths = lengthsSequence.slice(hlit, hlit + hdist); - var distances = buildCodes(distancesLengths); - - return { - codesTree : buildTree(codes, codesLengths), - distancesTree : buildTree(distances, distancesLengths) - }; - }; - - - _inflatingReader.prototype = new JSIO._ByteReaderBase(); - - - // internal instance fns - _inflatingReader.prototype._decodeItem = function() { - if (this._state == 2) return null; // end-of-blocks - - var item; - if(this._state === 0) { - this._blockFinal = this._bitReader.readBit(); - var blockType = this._bitReader.readLSB(2); - switch(blockType) { - case 0: - this._bitReader.align(); - var len = this._bitReader.readLSB(16); // low-byte first, as opposed to readNumber's HBF - var nlen = this._bitReader.readLSB(16); - if ((len & ~nlen) != len) this._throwError('Invalid block type 0 length', null, '_decodeItem'); - - item = {}; - item.itemType = 0; - item.array = this._bitReader.byteReader.read(len); - if (item.array.length < len) this._throwError('Incomplete block', null, '_decodeItem'); - if (this._blockFinal) this._state = 2; - return item; - case 1: - this._codesTree = staticCodes; - this._distancesTree = staticDistances; - this._state = 1; - break; - case 2: - var dTrees = readDynamicTrees(this._bitReader); - this._codesTree = dTrees.codesTree; - this._distancesTree = dTrees.distancesTree; - this._state = 1; - break; - default: - this._throwError('Invalid block type ('+ blockType +')', null, '_decodeItem'); - } - } - - item = {}; - - var p = this._codesTree; - while (!p.isLeaf) { p = this._bitReader.readBit() ? p.one : p.zero; } - if(p.index < 256) { - item.itemType = 2; - item.symbol = p.index; - } else if(p.index > 256) { - var lengthCode = p.index; - if(lengthCode > 285) this._throwError('Invalid length code', null, '_decodeItem'); - - var length = encodedLengthStart[lengthCode - 257]; - if(encodedLengthAdditionalBits[lengthCode - 257] > 0) { - length += this._bitReader.readLSB(encodedLengthAdditionalBits[lengthCode - 257]); - } - - p = this._distancesTree; - while (!p.isLeaf) { p = this._bitReader.readBit() ? p.one : p.zero; } - - var distanceCode = p.index; - var distance = encodedDistanceStart[distanceCode]; - if (encodedDistanceAdditionalBits[distanceCode] > 0) - distance += this._bitReader.readLSB(encodedDistanceAdditionalBits[distanceCode]); - - item.itemType = 3; - item.distance = distance; - item.length = length; - } else { - item.itemType = 1; - this._state = this._blockFinal ? 2 : 0; // EOB - } - return item; - }; - - - - // public instance functions - - _inflatingReader.prototype.readByte = function() { - var byte = this.read(1)[0]; - return (byte === null || byte === undefined) ? null : byte; - }; - - _inflatingReader.prototype.read = function(len) { - var b = this._buffer; // (since we use this so much...) - - // Keep reading until we get to the right length - while (this._bufferPosition+len > b.length) { - var item = this._decodeItem(); - if (item === null) { // EOF - len = b.length - this._bufferPosition; - break; - } - switch(item.itemType) { - case 0: - JSIO.massApply(b.push, b, item.array); - break; - case 2: - b.push(item.symbol); - break; - case 3: - var j = b.length - item.distance; - if (item.distance >= item.length) - JSIO.massApply(b.push, b, b.slice(j, j+item.length)); - // sometimes DEFLATE tries some trickery with "look-ahead" compression - else { - // this is basically just a repetition of the same string, plus some possible cutoff - var count = parseInt(item.length / item.distance); - var repArr = b.slice(j); - // http://stackoverflow.com/questions/202605/repeat-string-javascript/5450113#5450113 - while (count > 0) { - if (count & 1) JSIO.massApply( b.push, b, repArr); - if (count >>= 1) JSIO.massApply(repArr.push, repArr, repArr); - } - // add any remaining cutoff - var r; - if (r = item.length % item.distance) - JSIO.massApply(b.push, b, b.slice(j, j+r)); - } - break; - } - } - var bytes = b.slice(this._bufferPosition, this._bufferPosition+len); - this._bufferPosition += len; - this.position += len; - - if (this._bufferPosition > 0xC000) { - var shift = b.length - 0x8000; - if (shift > this._bufferPosition) shift = this._bufferPosition; - b.splice(0, shift); - this._bufferPosition -= shift; - } - - return bytes; - }; - - // initialization routine - once per type - (function(){ - - var codes = new Array(288); - var codesLengths = new Array(288); - var i=0; - for ( i = 0; i <= 143; i++) { - codes[i] = 0x0030 + i; - codesLengths[i] = 8; - } - for ( i = 144; i <= 255; i++) { - codes[i] = 0x0190 + i - 144; - codesLengths[i] = 9; - } - for ( i = 256; i <= 279; i++) { - codes[i] = 0x0000 + i - 256; - codesLengths[i] = 7; - } - for ( i = 280; i <= 287; i++) { - codes[i] = 0x00C0 + i - 280; - codesLengths[i] = 8; - } - staticCodes = buildTree(codes, codesLengths); - - var distances = new Array(32); - var distancesLengths = new Array(32); - for ( i = 0; i <= 31; i++) { - distances[i] = i; - distancesLengths[i] = 5; - } - staticDistances = buildTree(distances, distancesLengths); - })(); - - - JSIO.InflatingReader = _inflatingReader; + var version = "2.0 2012Feb"; + var typename = "JSIO.InflatingReader"; + + if (typeof JSIO._ByteReaderBase !== "function") + JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename); + + // ======================================================= + // _InternalBitReader is used internally in the InflatingReader class. + // + + JSIO.bitShiftTable = null; + + var bitShiftTableCalc = function () { + // do this once only, for all instances + if (JSIO.bitShiftTable) return; + + var bits = 8; + JSIO.bitShiftTable = { + LSB: new Array(bits), + MSB: new Array(bits) + } + for (var bp = 0; bp < bits; bp++) { + var lim = bits - bp; + JSIO.bitShiftTable.LSB[bp] = new Array(lim); + JSIO.bitShiftTable.MSB[bp] = new Array(lim); + + var maskLSB = 1 << bp; + var maskMSB = 1 << lim-1; + for (var len = 1; len <= lim; len++) { + JSIO.bitShiftTable.LSB[bp][len-1] = maskLSB; + JSIO.bitShiftTable.MSB[bp][len-1] = maskMSB; + maskLSB |= 1 << bp+len; + maskMSB |= 1 << lim-len-1; + } + } + }; + + var _InternalBitReader = function(reader) { + if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename + '._InternalBitReader.ctor'); + this.bitsLength = 0; + this.bits = 0; + this.byteReader = reader; + this._typeName = typename + "._InternalBitReader"; + this._version = version; + bitShiftTableCalc(); + }; + + _InternalBitReader.prototype._throwError = JSIO.throwError; + + _InternalBitReader.prototype.readBit = function() { + if (this.bitsLength === 0) { + var nextByte = this.byteReader.readByte(); + if (nextByte === null) this._throwError('Unexpected end of stream', null, 'readBit'); + this.bits = nextByte; + this.bitsLength = 8; + } + + var bit = (this.bits & 1 << 8-this.bitsLength) !== 0; + this.bitsLength--; + return bit; + }; + + _InternalBitReader.prototype.align = function() { this.bitsLength = 0; }; + + _InternalBitReader.prototype.readBits = function(len, type) { + var data = 0; + type = type || 'LSB'; + var tbl = JSIO.bitShiftTable[type]; + var dl = 0; + while (len > 0) { + if (this.bitsLength === 0) { + var nextByte = this.byteReader.readByte(); + if (nextByte === null) this._throwError('Unexpected end of stream', null, 'read'+type); + this.bits = nextByte; + this.bitsLength = 8; + } + + var maskLen = (this.bitsLength >= len) ? len : this.bitsLength; + var bitsPos = 8-this.bitsLength; + data |= (this.bits & tbl[bitsPos][maskLen-1]) >>> bitsPos << dl; + + dl += maskLen; + len -= maskLen; + this.bitsLength -= maskLen; + }; + return data; + }; + + _InternalBitReader.prototype.readLSB = function(len) { return this.readBits(len, 'LSB'); } + _InternalBitReader.prototype.readMSB = function(len) { return this.readBits(len, 'MSB'); } + + // + // ======================================================= + + + /* inflating ByteReader - RFC 1951 */ + var _inflatingReader = function(reader) { + if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename, 'ctor'); + this._byteReader = reader; + this._bitReader = new _InternalBitReader(reader); + this._buffer = []; + this._bufferPosition = 0; + this._state = 0; + this._blockFinal = false; + this._typeName = typename; + this._version = version; + return this; + }; + + + // shared fns and variables + + var staticCodes = null; + var staticDistances = null; + + var clenMap = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]; + + var buildCodes = function(lengths){ + var i=0; + var codes = new Array(lengths.length); + var maxBits = lengths[0]; + for (i=1; i<lengths.length; i++) { + if (maxBits < lengths[i]) maxBits = lengths[i]; + } + + var bitLengthsCount = new Array(maxBits + 1); + for (i=0; i<=maxBits; i++) bitLengthsCount[i]=0; + + for (i=0; i<lengths.length; i++) { + ++bitLengthsCount[lengths[i]]; + } + + var nextCode = new Array(maxBits + 1); + var code = 0; + bitLengthsCount[0] = 0; + for (var bits=1; bits<=maxBits; bits++) { + code = (code + bitLengthsCount[bits - 1]) << 1; + nextCode[bits] = code; + } + + for (i=0; i<codes.length; i++) { + var len = lengths[i]; + if (len !== 0) { + codes[i] = nextCode[len]; + nextCode[len]++; + } + } + return codes; + }; + + var buildTree = function(codes, lengths){ + var nonEmptyCodes = []; + for(var i=0; i<codes.length; ++i) { + if(lengths[i] > 0) { + var code = {}; + code.bits = codes[i]; + code.length = lengths[i]; + code.index = i; + nonEmptyCodes.push(code); + } + } + return buildTreeBranch(nonEmptyCodes, 0, 0); + }; + + + var buildTreeBranch = function(codes, prefix, prefixLength){ + if (codes.length === 0) return null; + + var zeros = []; + var ones = []; + var branch = {}; + branch.isLeaf = false; + for(var i=0; i<codes.length; ++i) { + if (codes[i].length == prefixLength && codes[i].bits == prefix) { + branch.isLeaf = true; + branch.index = codes[i].index; + break; + } + else { + var nextBit = ((codes[i].bits >> (codes[i].length - prefixLength - 1)) & 1) > 0; + if (nextBit) ones.push(codes[i]); + else zeros.push(codes[i]); + } + } + if(!branch.isLeaf) { + branch.zero = buildTreeBranch(zeros, (prefix << 1), prefixLength + 1); + branch.one = buildTreeBranch(ones, (prefix << 1) | 1, prefixLength + 1); + } + return branch; + }; + + + var encodedLengthStart = [3,4,5,6,7,8,9,10, + 11,13,15,17,19,23,27,31,35,43,51,59,67,83,99, + 115,131,163,195,227,258]; + + var encodedLengthAdditionalBits = [0,0,0,0,0,0,0,0, + 1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0]; + + var encodedDistanceStart = [1,2,3,4, 5,7,9, 13,17,25, 33,49,65, + 97,129,193,257,385,513,769,1025,1537,2049, + 3073,4097,6145,8193,12289,16385,24577]; + + var encodedDistanceAdditionalBits = [0,0,0,0,1,1,2,2,3,3,4,4, + 5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13]; + + + var readDynamicTrees = function(bitReader){ + var hlit = bitReader.readLSB(5) + 257; + var hdist = bitReader.readLSB(5) + 1; + var hclen = bitReader.readLSB(4) + 4; + var clen = new Array(19); + var i; + for (i=0; i<clen.length; i++) { clen[i] = 0; } + for (i=0; i<hclen; i++) { clen[clenMap[i]] = bitReader.readLSB(3); } + + var clenCodes = buildCodes(clen); + var clenTree = buildTree(clenCodes, clen); + + var lengthsSequence = []; + while(lengthsSequence.length < hlit + hdist) { + var p = clenTree; + while(!p.isLeaf) { p = bitReader.readBit() ? p.one : p.zero; } + + var code = p.index; + if (code <= 15) lengthsSequence.push(code); + else if (code == 16) { + // TODO: replace with faster repeat algorythm + var repeat = bitReader.readLSB(2) + 3; + for(var q=0; q<repeat; ++q){ + lengthsSequence.push(lengthsSequence[lengthsSequence.length - 1]); + } + } + else if (code == 17) { + var repeat1 = bitReader.readLSB(3) + 3; + for(var q1=0; q1<repeat1; ++q1) { + lengthsSequence.push(0); + } + } + else if (code == 18) { + var repeat2 = bitReader.readLSB(7) + 11; + for(var q2=0; q2<repeat2; ++q2){ + lengthsSequence.push(0); + } + } + } + + var codesLengths = lengthsSequence.slice(0, hlit); + var codes = buildCodes(codesLengths); + var distancesLengths = lengthsSequence.slice(hlit, hlit + hdist); + var distances = buildCodes(distancesLengths); + + return { + codesTree : buildTree(codes, codesLengths), + distancesTree : buildTree(distances, distancesLengths) + }; + }; + + + _inflatingReader.prototype = new JSIO._ByteReaderBase(); + + + // internal instance fns + _inflatingReader.prototype._decodeItem = function() { + if (this._state == 2) return null; // end-of-blocks + + var item; + if(this._state === 0) { + this._blockFinal = this._bitReader.readBit(); + var blockType = this._bitReader.readLSB(2); + switch(blockType) { + case 0: + this._bitReader.align(); + var len = this._bitReader.readLSB(16); // low-byte first, as opposed to readNumber's HBF + var nlen = this._bitReader.readLSB(16); + if ((len & ~nlen) != len) this._throwError('Invalid block type 0 length', null, '_decodeItem'); + + item = {}; + item.itemType = 0; + item.array = this._bitReader.byteReader.read(len); + if (item.array.length < len) this._throwError('Incomplete block', null, '_decodeItem'); + if (this._blockFinal) this._state = 2; + return item; + case 1: + this._codesTree = staticCodes; + this._distancesTree = staticDistances; + this._state = 1; + break; + case 2: + var dTrees = readDynamicTrees(this._bitReader); + this._codesTree = dTrees.codesTree; + this._distancesTree = dTrees.distancesTree; + this._state = 1; + break; + default: + this._throwError('Invalid block type ('+ blockType +')', null, '_decodeItem'); + } + } + + item = {}; + + var p = this._codesTree; + while (!p.isLeaf) { p = this._bitReader.readBit() ? p.one : p.zero; } + if(p.index < 256) { + item.itemType = 2; + item.symbol = p.index; + } else if(p.index > 256) { + var lengthCode = p.index; + if(lengthCode > 285) this._throwError('Invalid length code', null, '_decodeItem'); + + var length = encodedLengthStart[lengthCode - 257]; + if(encodedLengthAdditionalBits[lengthCode - 257] > 0) { + length += this._bitReader.readLSB(encodedLengthAdditionalBits[lengthCode - 257]); + } + + p = this._distancesTree; + while (!p.isLeaf) { p = this._bitReader.readBit() ? p.one : p.zero; } + + var distanceCode = p.index; + var distance = encodedDistanceStart[distanceCode]; + if (encodedDistanceAdditionalBits[distanceCode] > 0) + distance += this._bitReader.readLSB(encodedDistanceAdditionalBits[distanceCode]); + + item.itemType = 3; + item.distance = distance; + item.length = length; + } else { + item.itemType = 1; + this._state = this._blockFinal ? 2 : 0; // EOB + } + return item; + }; + + + + // public instance functions + + _inflatingReader.prototype.readByte = function() { + var oneByte = this.read(1)[0]; + return (oneByte === null || oneByte === undefined) ? null : oneByte; + }; + + _inflatingReader.prototype.read = function(len) { + var b = this._buffer; // (since we use this so much...) + + // Keep reading until we get to the right length + while (this._bufferPosition+len > b.length) { + var item = this._decodeItem(); + if (item === null) { // EOF + len = b.length - this._bufferPosition; + break; + } + switch(item.itemType) { + case 0: + JSIO.massApply(b.push, b, item.array); + break; + case 2: + b.push(item.symbol); + break; + case 3: + var j = b.length - item.distance; + if (item.distance >= item.length) + JSIO.massApply(b.push, b, b.slice(j, j+item.length)); + // sometimes DEFLATE tries some trickery with "look-ahead" compression + else { + // this is basically just a repetition of the same string, plus some possible cutoff + var count = parseInt(item.length / item.distance); + var repArr = b.slice(j); + // http://stackoverflow.com/questions/202605/repeat-string-javascript/5450113#5450113 + while (count > 0) { + if (count & 1) JSIO.massApply( b.push, b, repArr); + if (count >>= 1) JSIO.massApply(repArr.push, repArr, repArr); + } + // add any remaining cutoff + var r; + if (r = item.length % item.distance) + JSIO.massApply(b.push, b, b.slice(j, j+r)); + } + break; + } + } + var bytes = b.slice(this._bufferPosition, this._bufferPosition+len); + this._bufferPosition += len; + this.position += len; + + if (this._bufferPosition > 0xC000) { + var shift = b.length - 0x8000; + if (shift > this._bufferPosition) shift = this._bufferPosition; + b.splice(0, shift); + this._bufferPosition -= shift; + } + + return bytes; + }; + + // initialization routine - once per type + (function(){ + + var codes = new Array(288); + var codesLengths = new Array(288); + var i=0; + for ( i = 0; i <= 143; i++) { + codes[i] = 0x0030 + i; + codesLengths[i] = 8; + } + for ( i = 144; i <= 255; i++) { + codes[i] = 0x0190 + i - 144; + codesLengths[i] = 9; + } + for ( i = 256; i <= 279; i++) { + codes[i] = 0x0000 + i - 256; + codesLengths[i] = 7; + } + for ( i = 280; i <= 287; i++) { + codes[i] = 0x00C0 + i - 280; + codesLengths[i] = 8; + } + staticCodes = buildTree(codes, codesLengths); + + var distances = new Array(32); + var distancesLengths = new Array(32); + for ( i = 0; i <= 31; i++) { + distances[i] = i; + distancesLengths[i] = 5; + } + staticDistances = buildTree(distances, distancesLengths); + })(); + + + JSIO.InflatingReader = _inflatingReader; })(); @@ -1713,460 +1713,537 @@ // This work is licensed under the GPLv3. (function(){ - var version = "2.0 2012Feb"; - var typename = "Zipfile"; - - if (typeof JSIO.BinaryUrlStream !== "function") JSIO.throwError('This extension requires JSIO.BinaryUrlStream.js v2.0', typename); - if (typeof JSIO.TextDecoder !== "object") JSIO.throwError('This extension requires JSIO.TextDecoder.js v2.0', typename); - if (typeof JSIO.TextReader !== "function") JSIO.throwError('This extension requires JSIO.TextReader.js v2.0', typename); - if (typeof JSIO.Crc32 !== "function") JSIO.throwError('This extension requires JSIO.Crc32.js v2.0', typename); - if (typeof JSIO.InflatingReader !== "function") JSIO.throwError('This extension requires JSIO.InflatingReader.js v2.0', typename); - - // ======================================================= - function ZipEntry(zip) { - this.zipfile = zip; - this._typename = "ZipEntry"; - this._version = version; - this._crcCalculator = null; - } - - ZipEntry.prototype._throwError = JSIO.throwError; - - // return byte array or string - ZipEntry.prototype.extract = function(callback, asString) { - this.contentType = JSIO.guessFileType(this.name); - asString = asString || ( this.contentType == JSIO.FileType.Text || - this.contentType == JSIO.FileType.XML); - var thisEntry = this; - - if (this.compressionMethod !== 0 && this.compressionMethod != 8) - this._throwError('Unsupported compression method: ' + this.compressionMethod, null, 'extract'); - - var reader = (asString) ? this.openTextReader(thisEntry.utf8 ? JSIO.TextDecoder.UTF8 : JSIO.TextDecoder.ANSI) : this.openBinaryReader(); - - // diagnostic purpose only; tag the reader with the entry name - reader.zipEntryName = thisEntry.name; - - if (typeof callback != "function") { - // synchronous - var result = reader.readToEnd(); - this.verifyCrc32(); - return result; - } - - // asynchronous - reader.beginReadToEnd(function(result){ - try { - thisEntry.verifyCrc32(); - callback(thisEntry, result); - } - catch (exc1) { - callback(thisEntry, exc1); - } - }); - return null; - }; - - - // open a ByteReader on the entry, which will read binary - // content from the compressed stream. - ZipEntry.prototype.openBinaryReader = function() { - var reader = - new JSIO.StreamSegmentReader(this.zipfile.binaryStream, - this.offset + this.lengthOfHeader, - this.compressedSize); - if (this.compressionMethod === 0) { - this._crcCalculator = new JSIO.Crc32Reader(reader); - } - else { - var inflator = new JSIO.InflatingReader(reader); - this._crcCalculator = new JSIO.Crc32Reader(inflator); - } - // Whether compressed or not, the source ByteReader in each case - // is wrapped in a second ByteReader object that calculates CRC - // as it reads. That way, after all reading is complete, the - // caller can check the calcuated CRC against the expected CRC. - return this._crcCalculator; - }; - - // open a TextReader on the entry, to read text from the - // compressed stream. - ZipEntry.prototype.openTextReader = function(decoderKind) { - var reader = this.openBinaryReader(); - decoderKind = decoderKind || JSIO.TextDecoder.UTF8; - var d = new decoderKind(reader); - var textReader = new JSIO.TextReader(d); - d._parent = textReader; // store a reference, for diagnostic purposes only - return textReader; - }; - - // verify the CRC on the entry. - // call this after all bytes have been read. - ZipEntry.prototype.verifyCrc32 = function() { - var computedCrc = this._crcCalculator.crc32(); - var rc = false; // CRC FAIL - if (this.crc32 != computedCrc) { - var msg = "WARNING: CRC check failed: " + - "entry(" + this.name + ") " + - "computed(" + JSIO.decimalToHexString(computedCrc,8) + ") " + - "expected(" + JSIO.decimalToHexString(this.crc32,8) + ") "; - this.zipfile.status.push(msg); - } else { - rc = true; // OK - if (this.zipfile.verbose>2) { - this.zipfile.status.push("INFO: CRC check ok: 0x" + - JSIO.decimalToHexString(this.crc32,8)); - } - } - return rc; - }; - - - // ctor - ZipFile = function(fileUrl, callback, verbosity) { - if (! (this instanceof arguments.callee) ) JSIO.throwError('You must use new to instantiate this class', typename, 'ctor'); - - this.verbose = verbosity || 0; - this.entries = []; - this.entryNames = []; - this.status = []; - this._version = version; - this._typename = "ZipFile"; - this._throwError = JSIO.throwError; - - var thisZipFile = this; - - // Could use a back-tracking reader for the central directory, but - // there's no point, since all the zip data is held in memory anyway. - - /* function ReadCentralDirectory(){ - var posn = thisZipFile.binaryStream.length - 64; - var maxSeekback = Math.Max(s.Length - 0x4000, 10); - var success = false; - var nTries = 0; - do - { - thisZipFile.binaryStream.Seek(posn, SeekOrigin.Begin); - var bytesRead = thisZipFile.binaryStream.findSignature(thisZipFile.Signatures.EndOfCentralDirectory); - if (bytesRead != -1) - success = true; - else - { - nTries++; - // increasingly larger - posn -= (32 * (nTries + 1) * nTries); - if (posn < 0) posn = 0; // BOF - } - } - while (!success && posn > maxSeekback); - if (!success) { - thisZipFile.status.push("cannot find End of Central Directory"); - return; - } - } */ - - - function DateFromPackedFormat(packed) { - if (packed == 0xFFFF || packed === 0) return new Date(1995, 0, 1, 0,0,0,0); - - var packedTime = packed & 0x0000ffff; - var packedDate = ((packed & 0xffff0000) >> 16); - - var year = 1980 + ((packedDate & 0xFE00) >> 9); - var month = ((packedDate & 0x01E0) >> 5) -1; - var day = packedDate & 0x001F; - - var hour = (packedTime & 0xF800) >> 11; - var minute = (packedTime & 0x07E0) >> 5; - var second = (packedTime & 0x001F) * 2; - - // Validation and error checking. - // This is not foolproof but will catch most errors. - - // I can't believe how many different ways applications - // can mess up a simple date format. - - if (second >= 60) { minute++; second = 0; } - if (minute >= 60) { hour++; minute = 0; } - if (hour >= 24) { day++; hour = 0; } - var success = false; - var d; - try { - d = new Date(year, month, day, hour, minute, second, 0); - success= true; - } - catch (exc1) { - if (year == 1980 && (month === 0 || day === 0)) { - try { - d = new Date(1980, 0, 1, hour, minute, second, 0); - success= true; - } - catch (exc2) { - try { - d = new Date(1980, 0, 1, 0, 0, 0, 0); - success= true; - } - catch (exc3) { } // how could this fail?? - } - } - else { - try { - if (year < 1980) year = 1980; - if (year > 2030) year = 2030; - if (month < 1) month = 1; - if (month > 12) month = 12; - if (day < 1) day = 1; - if (day > 31) day = 31; - if (minute < 0) minute = 0; - if (minute > 59) minute = 59; - if (second < 0) second = 0; - if (second > 59) second = 59; - d = new Date(year, month-1, day, hour, minute, second, 0); - success= true; - } - catch (exc4){} - } - } - if (!success) this._throwError('Bad date/time value in this ZIP file', null, 'DateFromPackedFormat'); - return d; - } - - - function ReadZipEntries () { - // read only once - if (thisZipFile.entryNames.length === 0){ - var e; - while ((e = ReadZipEntry()) !== null) { - thisZipFile.entries.push(e); - thisZipFile.entryNames.push(e.name); - } - } - } - - - function ReadZipEntry () { - var offset = thisZipFile.binaryStream.position; - var sig = thisZipFile.binaryStream.readNumber(4); - if (sig == ZipFile.Signatures.DirEntry) { - // after all entries, comes the central directory - if (thisZipFile.verbose > 0) { - thisZipFile.status.push("INFO: at offset 0x" + - JSIO.decimalToHexString(offset) + - ", found start of Zip Directory."); - } - // all done reading - return null; - } - if (sig != ZipFile.Signatures.Entry) { - thisZipFile.status.push("WARNING: at offset 0x" + - JSIO.decimalToHexString(offset) + - ", found unexpected signature: 0x" + - JSIO.decimalToHexString(sig)); - return null; - } - - var entry = new ZipEntry(thisZipFile); - entry.offset = offset; - entry.versionNeeded = thisZipFile.binaryStream.readNumber(2); - entry.bitField = thisZipFile.binaryStream.readNumber(2); - entry.compressionMethod = thisZipFile.binaryStream.readNumber(2); - var timeBlob = thisZipFile.binaryStream.readNumber(4); - entry.lastModified = DateFromPackedFormat(timeBlob); - entry.crc32 = thisZipFile.binaryStream.readNumber(4); - entry.compressedSize = thisZipFile.binaryStream.readNumber(4); - entry.uncompressedSize = thisZipFile.binaryStream.readNumber(4); - - if ((entry.bitField & 0x01) == 0x01){ - thisZipFile.status.push("This zipfile uses Encryption, which is not supported by ZipFile.js."); - return null; - } - - entry.utf8 = ((entry.bitField & 0x0800) == 0x0800); - - if ((entry.bitField & 0x0008) == 0x0008){ - thisZipFile.status.push("This zipfile uses a bit 3 trailing data descriptor, which is not supported by ZipFile.js."); - return null; - } - - if (entry.compressedSize == 0xFFFFFFFF || - entry.uncompressedSize == 0xFFFFFFFF) { - thisZipFile.status.push("This zipfile uses ZIP64, which is not supported by ZipFile.js"); - return null; - } - - var filenameLength = thisZipFile.binaryStream.readNumber(2); - var extraFieldLength = thisZipFile.binaryStream.readNumber(2); - - thisZipFile.status.push("INFO: filename length= " + filenameLength); - - // we've read 30 bytes of metadata so far - var bytesRead = 30 + filenameLength + extraFieldLength; - - if (entry.utf8) { - thisZipFile.status.push("INFO: before filename, position= 0x" + - JSIO.decimalToHexString( thisZipFile.binaryStream.position )); - var binReader = - new JSIO.StreamSegmentReader(thisZipFile.binaryStream, - thisZipFile.binaryStream.position, - filenameLength); - var utf8Decoder = new JSIO.TextDecoder.UTF8(binReader); - var textReader = new JSIO.TextReader(utf8Decoder); - entry.name = textReader.readToEnd(); - - // advance the filepointer: - thisZipFile.binaryStream.seek(filenameLength, - JSIO.SeekOrigin.Current, - thisZipFile); - - thisZipFile.status.push("INFO: after filename, position= 0x" + - JSIO.decimalToHexString( thisZipFile.binaryStream.position )); - } - else { - entry.name = thisZipFile.binaryStream.readString(filenameLength); - } - - // There are a bunch of things in the "extra" header, thisZipFile we - // could parse, like timestamps and other things. This class - // only identifies and separates them. - - // More info here: http://www.pkware.com/documents/casestudies/APPNOTE.TXT - - var extraPos = 0; - entry.extra = []; - while (extraPos < extraFieldLength) { - var extraBlock = { - type: thisZipFile.binaryStream.readNumber(2), - size: thisZipFile.binaryStream.readNumber(2) - }; - extraBlock.typeDescription = ZipFile.ExtraFieldTypes[extraBlock.type]; - extraBlock.data = thisZipFile.binaryStream.read(extraBlock.size); - entry.extra.push(extraBlock); - extraPos += 4 + extraBlock.size; - } - - if (thisZipFile.verbose > 1) { - thisZipFile.status.push("INFO: at offset 0x" + - JSIO.decimalToHexString(entry.offset) + - ", found entry '" + entry.name + "' fnl(" + - filenameLength + ") efl(" + - extraFieldLength +")"); - } - - if (extraFieldLength > 0) { - if (thisZipFile.verbose > 0) { - thisZipFile.status.push("INFO: entry " + entry.name + " has " + - extraFieldLength + " bytes of " + - "extra metadata (ID'd but ignored)"); - } - } - - entry.lengthOfHeader = bytesRead; - entry.totalEntrySize = entry.lengthOfHeader + entry.compressedSize; - - // seek past the data without reading it. We will read on Extract() - if (thisZipFile.verbose > 1) { - thisZipFile.status.push("INFO: seek 0x" + - JSIO.decimalToHexString(entry.compressedSize) + - " (" + entry.compressedSize + ") bytes"); - } - - thisZipFile.binaryStream.seek(entry.compressedSize, - JSIO.SeekOrigin.Current, - thisZipFile); - - return entry; - } - - - var parseZipFile = function(bfr){ - try { - if (bfr.req.status == 200) { - var sig = thisZipFile.binaryStream.readNumber(4); - if (sig != ZipFile.Signatures.Entry){ - thisZipFile.status.push("WARNING: this file does not appear to be a zip file"); - } else { - thisZipFile.binaryStream.seek(0, JSIO.SeekOrigin.Begin); - ReadZipEntries(); - if (thisZipFile.verbose > 0) { - thisZipFile.status.push("INFO: read " + thisZipFile.entries.length + " entries"); - } - } - } - else { - thisZipFile.status.push("ERROR: the URL could not be read (" + - bfr.req.status + " " + bfr.req.statusText + ")"); - } - callback(thisZipFile); - } - catch (exc1) - { - thisZipFile.status.push("Exception: " + exc1.message); - callback(thisZipFile); - } - }; - - this.binaryStream = new JSIO.BinaryUrlStream(fileUrl, parseZipFile); - - return this; - }; - - - ZipFile.Signatures = { - Entry : 0x04034b50, - EndOfCentralDirectory : 0x06054b50, - DirEntry : 0x02014b50 - }; - - ZipFile.Version = version; - - ZipFile.EncryptionAlgorithm = { - None : 0, - PkzipWeak : 1, - WinZipAes : 2 - }; - - ZipFile.ExtraFieldTypes = {}; - ZipFile.ExtraFieldTypes[0x0001] = 'Zip64 Extended Info'; - ZipFile.ExtraFieldTypes[0x0007] = 'AV Info'; - ZipFile.ExtraFieldTypes[0x0008] = 'Extended Language Encoding Data (PFS)'; - ZipFile.ExtraFieldTypes[0x0009] = 'OS/2'; - ZipFile.ExtraFieldTypes[0x000a] = 'NTFS '; - ZipFile.ExtraFieldTypes[0x000c] = 'OpenVMS'; - ZipFile.ExtraFieldTypes[0x000d] = 'UNIX'; - ZipFile.ExtraFieldTypes[0x000e] = 'File Stream and Fork Descriptors'; - ZipFile.ExtraFieldTypes[0x000f] = 'Patch Descriptor'; - ZipFile.ExtraFieldTypes[0x0014] = 'PKCS#7 Store for X.509 Certificates'; - ZipFile.ExtraFieldTypes[0x0015] = 'X.509 Certificate ID and Signature (Individual File)'; - ZipFile.ExtraFieldTypes[0x0016] = 'X.509 Certificate ID (Central Directory)'; - ZipFile.ExtraFieldTypes[0x0017] = 'Strong Encryption Header'; - ZipFile.ExtraFieldTypes[0x0018] = 'Record Management Controls'; - ZipFile.ExtraFieldTypes[0x0019] = 'PKCS#7 Encryption Recipient Certificate List'; - ZipFile.ExtraFieldTypes[0x0065] = 'IBM S/390 (Z390), AS/400 (I400) attributes (uncompressed)'; - ZipFile.ExtraFieldTypes[0x0066] = 'IBM S/390 (Z390), AS/400 (I400) attributes (compressed)'; - ZipFile.ExtraFieldTypes[0x4690] = 'POSZIP 4690 (reserved) '; - ZipFile.ExtraFieldTypes[0x07c8] = 'Macintosh'; - ZipFile.ExtraFieldTypes[0x2605] = 'ZipIt Macintosh'; - ZipFile.ExtraFieldTypes[0x2705] = 'ZipIt Macintosh 1.3.5+'; - ZipFile.ExtraFieldTypes[0x2805] = 'ZipIt Macintosh 1.3.5+'; - ZipFile.ExtraFieldTypes[0x334d] = 'Info-ZIP Macintosh'; - ZipFile.ExtraFieldTypes[0x4341] = 'Acorn/SparkFS '; - ZipFile.ExtraFieldTypes[0x4453] = 'Windows NT security descriptor (binary ACL)'; - ZipFile.ExtraFieldTypes[0x4704] = 'VM/CMS'; - ZipFile.ExtraFieldTypes[0x470f] = 'MVS'; - ZipFile.ExtraFieldTypes[0x4b46] = 'FWKCS MD5'; - ZipFile.ExtraFieldTypes[0x4c41] = 'OS/2 access control list (text ACL)'; - ZipFile.ExtraFieldTypes[0x4d49] = 'Info-ZIP OpenVMS'; - ZipFile.ExtraFieldTypes[0x4f4c] = 'Xceed original location extra field'; - ZipFile.ExtraFieldTypes[0x5356] = 'AOS/VS (ACL)'; - ZipFile.ExtraFieldTypes[0x5455] = 'extended timestamp'; - ZipFile.ExtraFieldTypes[0x554e] = 'Xceed unicode extra field'; - ZipFile.ExtraFieldTypes[0x5855] = 'Info-ZIP UNIX (original, also OS/2, NT, etc)'; - ZipFile.ExtraFieldTypes[0x6375] = 'Info-ZIP Unicode Comment Extra Field'; - ZipFile.ExtraFieldTypes[0x6542] = 'BeOS/BeBox'; - ZipFile.ExtraFieldTypes[0x7075] = 'Info-ZIP Unicode Path Extra Field'; - ZipFile.ExtraFieldTypes[0x756e] = 'ASi UNIX'; - ZipFile.ExtraFieldTypes[0x7855] = 'Info-ZIP UNIX (new)'; - ZipFile.ExtraFieldTypes[0xa220] = 'Microsoft Open Packaging Growth Hint'; - ZipFile.ExtraFieldTypes[0xfd4a] = 'SMS/QDOS'; + var version = "2.0 2012Feb"; + var typename = "Zipfile"; + + if (typeof JSIO.BinaryUrlStream !== "function") JSIO.throwError('This extension requires JSIO.BinaryUrlStream.js v2.0', typename); + if (typeof JSIO.TextDecoder !== "object") JSIO.throwError('This extension requires JSIO.TextDecoder.js v2.0', typename); + if (typeof JSIO.TextReader !== "function") JSIO.throwError('This extension requires JSIO.TextReader.js v2.0', typename); + if (typeof JSIO.Crc32 !== "function") JSIO.throwError('This extension requires JSIO.Crc32.js v2.0', typename); + if (typeof JSIO.InflatingReader !== "function") JSIO.throwError('This extension requires JSIO.InflatingReader.js v2.0', typename); + + // ======================================================= + function ZipEntry(zip) { + this.zipfile = zip; + this._typename = "ZipEntry"; + this._version = version; + this._crcCalculator = null; + } + + ZipEntry.prototype._throwError = JSIO.throwError; + + // return byte array or string + ZipEntry.prototype.extract = function(callback, asString) { + this.contentType = JSIO.guessFileType(this.name); + asString = asString || ( this.contentType == JSIO.FileType.Text || + this.contentType == JSIO.FileType.XML); + var thisEntry = this; + + if (this.compressionMethod !== 0 && this.compressionMethod != 8) + this._throwError('Unsupported compression method: ' + this.compressionMethod, null, 'extract'); + + var reader = (asString) ? this.openTextReader(thisEntry.utf8 ? JSIO.TextDecoder.UTF8 : JSIO.TextDecoder.ANSI) : this.openBinaryReader(); + + // diagnostic purpose only; tag the reader with the entry name + reader.zipEntryName = thisEntry.name; + + if (typeof callback != "function") { + // synchronous + var result = reader.readToEnd(); + this.verifyCrc32(); + return result; + } + + // asynchronous + reader.beginReadToEnd(function(result){ + try { + thisEntry.verifyCrc32(); + callback(thisEntry, result); + } + catch (exc1) { + this.zipfile.status.push("EXCEPTION: " + exc1.message); + callback(thisEntry, exc1); + } + }); + return null; + }; + + + // open a ByteReader on the entry, which will read binary + // content from the compressed stream. + ZipEntry.prototype.openBinaryReader = function() { + var reader = + new JSIO.StreamSegmentReader(this.zipfile.binaryStream, + this.offset + this.lengthOfHeader, + this.compressedSize); + if (this.compressionMethod === 0) { + this._crcCalculator = new JSIO.Crc32Reader(reader); + } + else { + var inflator = new JSIO.InflatingReader(reader); + this._crcCalculator = new JSIO.Crc32Reader(inflator); + } + // Whether compressed or not, the source ByteReader in each case + // is wrapped in a second ByteReader object that calculates CRC + // as it reads. That way, after all reading is complete, the + // caller can check the calcuated CRC against the expected CRC. + return this._crcCalculator; + }; + + // open a TextReader on the entry, to read text from the + // compressed stream. + ZipEntry.prototype.openTextReader = function(decoderKind) { + var reader = this.openBinaryReader(); + decoderKind = decoderKind || JSIO.TextDecoder.UTF8; + var d = new decoderKind(reader); + var textReader = new JSIO.TextReader(d); + d._parent = textReader; // store a reference, for diagnostic purposes only + return textReader; + }; + + // verify the CRC on the entry. + // call this after all bytes have been read. + ZipEntry.prototype.verifyCrc32 = function() { + var computedCrc = this._crcCalculator.crc32(); + var rc = false; // CRC FAIL + if (this.crc32 != computedCrc) { + var msg = "WARNING: CRC check failed: " + + "entry(" + this.name + ") " + + "computed(" + JSIO.decimalToHexString(computedCrc,8) + ") " + + "expected(" + JSIO.decimalToHexString(this.crc32,8) + ") "; + this.zipfile.status.push(msg); + } else { + rc = true; // OK + if (this.zipfile.verbose>2) { + this.zipfile.status.push("INFO: CRC check ok: 0x" + + JSIO.decimalToHexString(this.crc32,8)); + } + } + return rc; + }; + + + // ctor + ZipFile = function(fileUrl, callback, verbosity) { + if (! (this instanceof arguments.callee) ) JSIO.throwError('You must use new to instantiate this class', typename, 'ctor'); + + this.verbose = verbosity || 0; + this.entries = []; + this.entryNames = []; + this.status = []; + this._version = version; + this._typename = "ZipFile"; + this._throwError = JSIO.throwError; + + var thisZipFile = this; + + // Could use a back-tracking reader for the central directory, but + // there's no point, since all the zip data is held in memory anyway. + + /* function ReadCentralDirectory(){ + var posn = thisZipFile.binaryStream.length - 64; + var maxSeekback = Math.Max(s.Length - 0x4000, 10); + var success = false; + var nTries = 0; + do + { + thisZipFile.binaryStream.Seek(posn, SeekOrigin.Begin); + var bytesRead = thisZipFile.binaryStream.findSignature(thisZipFile.Signatures.EndOfCentralDirectory); + if (bytesRead != -1) + success = true; + else + { + nTries++; + // increasingly larger + posn -= (32 * (nTries + 1) * nTries); + if (posn < 0) posn = 0; // BOF + } + } + while (!success && posn > maxSeekback); + if (!success) { + thisZipFile.status.push("cannot find End of Central Directory"); + return; + } + } */ + + + function DateFromPackedFormat(packed) { + if (packed == 0xFFFF || packed === 0) return new Date(1995, 0, 1, 0,0,0,0); + + var packedTime = packed & 0x0000ffff; + var packedDate = ((packed & 0xffff0000) >> 16); + + var year = 1980 + ((packedDate & 0xFE00) >> 9); + var month = ((packedDate & 0x01E0) >> 5) -1; + var day = packedDate & 0x001F; + + var hour = (packedTime & 0xF800) >> 11; + var minute = (packedTime & 0x07E0) >> 5; + var second = (packedTime & 0x001F) * 2; + + // Validation and error checking. + // This is not foolproof but will catch most errors. + + // I can't believe how many different ways applications + // can mess up a simple date format. + + if (second >= 60) { minute++; second = 0; } + if (minute >= 60) { hour++; minute = 0; } + if (hour >= 24) { day++; hour = 0; } + var success = false; + var d; + try { + d = new Date(year, month, day, hour, minute, second, 0); + success= true; + } + catch (exc1) { + if (year == 1980 && (month === 0 || day === 0)) { + try { + d = new Date(1980, 0, 1, hour, minute, second, 0); + success= true; + } + catch (exc2) { + try { + d = new Date(1980, 0, 1, 0, 0, 0, 0); + success= true; + } + catch (exc3) { } // how could this fail?? + } + } + else { + try { + if (year < 1980) year = 1980; + if (year > 2030) year = 2030; + if (month < 1) month = 1; + if (month > 12) month = 12; + if (day < 1) day = 1; + if (day > 31) day = 31; + if (minute < 0) minute = 0; + if (minute > 59) minute = 59; + if (second < 0) second = 0; + if (second > 59) second = 59; + d = new Date(year, month-1, day, hour, minute, second, 0); + success= true; + } + catch (exc4){} + } + } + if (!success) this._throwError('Bad date/time value in this ZIP file', null, 'DateFromPackedFormat'); + return d; + } + + + function ReadZipEntries () { + // read only once + if (thisZipFile.entryNames.length === 0){ + var e; + while ((e = ReadZipEntry()) !== null) { + thisZipFile.entries.push(e); + thisZipFile.entryNames.push(e.name); + } + } + } + + + function ReadZipEntry () { + var offset = thisZipFile.binaryStream.position; + var sig = thisZipFile.binaryStream.readNumber(4); + if (sig == ZipFile.Signatures.DirEntry) { + // after all entries, comes the central directory + if (thisZipFile.verbose > 2) { + thisZipFile.status.push("INFO: at offset 0x" + + JSIO.decimalToHexString(offset) + + ", found start of Zip Directory."); + } + // all done reading + return null; + } + if (sig != ZipFile.Signatures.Entry) { + thisZipFile.status.push("WARNING: at offset 0x" + + JSIO.decimalToHexString(offset) + + ", found unexpected signature: 0x" + + JSIO.decimalToHexString(sig)); + return null; + } + + var entry = new ZipEntry(thisZipFile); + entry.offset = offset; + entry.versionNeeded = thisZipFile.binaryStream.readNumber(2); + entry.bitField = thisZipFile.binaryStream.readNumber(2); + entry.compressionMethod = thisZipFile.binaryStream.readNumber(2); + var timeBlob = thisZipFile.binaryStream.readNumber(4); + entry.lastModified = DateFromPackedFormat(timeBlob); + entry.crc32 = thisZipFile.binaryStream.readNumber(4); + entry.compressedSize = thisZipFile.binaryStream.readNumber(4); + entry.uncompressedSize = thisZipFile.binaryStream.readNumber(4); + + if (thisZipFile.verbose>2) { + thisZipFile.status.push("INFO: crc32 0x" + + JSIO.decimalToHexString(entry.crc32) + + " (" + entry.crc32 + ")"); + thisZipFile.status.push("INFO: compressedSize 0x" + + JSIO.decimalToHexString(entry.compressedSize) + + " (" + entry.compressedSize + ") bytes"); + thisZipFile.status.push("INFO: uncompressedSize 0x" + + JSIO.decimalToHexString(entry.uncompressedSize) + + " (" + entry.uncompressedSize + ") bytes"); + } + if ((entry.bitField & 0x01) == 0x01){ + thisZipFile.status.push("This zipfile uses Encryption, which is not supported by ZipFile.js."); + return null; + } + + entry.utf8 = ((entry.bitField & 0x0800) == 0x0800); + + if (entry.compressedSize == 0xFFFFFFFF || + entry.uncompressedSize == 0xFFFFFFFF) { + thisZipFile.status.push("This zipfile uses ZIP64, which is not supported by ZipFile.js"); + return null; + } + + var filenameLength = thisZipFile.binaryStream.readNumber(2); + var extraFieldLength = thisZipFile.binaryStream.readNumber(2); + + if (thisZipFile.verbose>2) { + thisZipFile.status.push("INFO: filename length= " + filenameLength); + } + + // we've read 30 bytes of metadata so far + var bytesRead = 30 + filenameLength + extraFieldLength; + + if (entry.utf8) { + thisZipFile.status.push("INFO: before filename, position= 0x" + + JSIO.decimalToHexString( thisZipFile.binaryStream.position )); + var binReader = + new JSIO.StreamSegmentReader(thisZipFile.binaryStream, + thisZipFile.binaryStream.position, + filenameLength); + var utf8Decoder = new JSIO.TextDecoder.UTF8(binReader); + var textReader = new JSIO.TextReader(utf8Decoder); + entry.name = textReader.readToEnd(); + + // advance the filepointer: + thisZipFile.binaryStream.seek(filenameLength, + JSIO.SeekOrigin.Current, + thisZipFile); + + thisZipFile.status.push("INFO: after filename, position= 0x" + + JSIO.decimalToHexString( thisZipFile.binaryStream.position )); + } + else { + entry.name = thisZipFile.binaryStream.readString(filenameLength); + } + + // There are a bunch of things in the "extra" header, thisZipFile we + // could parse, like timestamps and other things. This class + // only identifies and separates them. + + // More info here: http://www.pkware.com/documents/casestudies/APPNOTE.TXT + + var extraPos = 0; + entry.extra = []; + while (extraPos < extraFieldLength) { + var extraBlock = { + type: thisZipFile.binaryStream.readNumber(2), + size: thisZipFile.binaryStream.readNumber(2) + }; + extraBlock.typeDescription = ZipFile.ExtraFieldTypes[extraBlock.type]; + extraBlock.data = thisZipFile.binaryStream.read(extraBlock.size); + entry.extra.push(extraBlock); + extraPos += 4 + extraBlock.size; + } + + if (thisZipFile.verbose > 1) { + thisZipFile.status.push("INFO: at offset 0x" + + JSIO.decimalToHexString(entry.offset) + + ", found entry '" + entry.name + "' fnl(" + + filenameLength + ") efl(" + + extraFieldLength +")"); + } + + if (extraFieldLength > 0) { + if (thisZipFile.verbose > 0) { + thisZipFile.status.push("INFO: entry " + entry.name + " has " + + extraFieldLength + " bytes of " + + "extra metadata (ID'd but ignored)"); + } + } + + entry.lengthOfHeader = bytesRead; + if (thisZipFile.verbose>2) { + thisZipFile.status.push("INFO: lengthOfHeader 0x" + + JSIO.decimalToHexString(entry.lengthOfHeader) + + " (" + entry.lengthOfHeader + ")"); + } + + /* + Data descriptor Offset Bytes Description[25] + 0 0/4 Optional data descriptor signature = 0x08074b50 + 0/4 4 CRC-32 + 4/8 4 Compressed size + 8/12 4 Uncompressed size + */ + + if ((entry.bitField & 0x0008) == 0x0008){ + thisZipFile.status.push("INFO: This zipfile uses a bit 3 trailing data descriptor"); + // return null; + var currentPosition = thisZipFile.binaryStream.position; + var dataDescLength = 0; + if (thisZipFile.verbose>2) { + thisZipFile.status.push("INFO: currentPosition 0x" + + JSIO.decimalToHexString(thisZipFile.binaryStream.position) + + " (" + thisZipFile.binaryStream.position + ")"); + thisZipFile.status.push("INFO: length 0x" + + JSIO.decimalToHexString(thisZipFile.binaryStream.length) + + " (" + thisZipFile.binaryStream.length + ")"); + } + + for (var i=0; i<128; i++) { // maximum search backwards from end for signature is 128 bytes + thisZipFile.binaryStream.seek(thisZipFile.binaryStream.length-i-4,JSIO.SeekOrigin.Begin); + var num = thisZipFile.binaryStream.readNumber(4) + if (thisZipFile.verbose>2) { + thisZipFile.status.push("INFO: ["+i+"] 0x" + + JSIO.decimalToHexString(num) + + " (" + num + ")"); + } + if (num == ZipFile.Signatures.DataDescriptor) { + sig = num; + entry.crc32 = thisZipFile.binaryStream.readNumber(4); + entry.compressedSize = thisZipFile.binaryStream.readNumber(4); + entry.uncompressedSize = thisZipFile.binaryStream.readNumber(4); + dataDescLength = 16; + break; + } + } + + if (thisZipFile.verbose>2) { + thisZipFile.status.push("INFO: sig 0x" + + JSIO.decimalToHexString(sig) + + " (" + sig + ")"); + thisZipFile.status.push("INFO: crc32 0x" + + JSIO.decimalToHexString(entry.crc32) + + " (" + entry.crc32 + ")"); + thisZipFile.status.push("INFO: compressedSize 0x" + + JSIO.decimalToHexString(entry.compressedSize) + + " (" + entry.compressedSize + ") bytes"); + thisZipFile.status.push("INFO: uncompressedSize 0x" + + JSIO.decimalToHexString(entry.uncompressedSize) + + " (" + entry.uncompressedSize + ") bytes"); + thisZipFile.status.push("INFO: lengthOfHeader 0x" + + JSIO.decimalToHexString(entry.lengthOfHeader) + + " (" + entry.lengthOfHeader + ")"); + } + + thisZipFile.binaryStream.position = currentPosition+dataDescLength; + } + + entry.totalEntrySize = entry.lengthOfHeader + entry.compressedSize; + + // seek past the data without reading it. We will read on Extract() + if (thisZipFile.verbose > 1) { + thisZipFile.status.push("INFO: seek 0x" + + JSIO.decimalToHexString(entry.compressedSize) + + " (" + entry.compressedSize + ") bytes"); + } + + thisZipFile.binaryStream.seek(entry.compressedSize, + JSIO.SeekOrigin.Current, + thisZipFile); + + return entry; + } + + + var parseZipFile = function(bfr){ + try { + if (bfr.req.status == 200) { + var sig = thisZipFile.binaryStream.readNumber(4); + if (sig != ZipFile.Signatures.Entry){ + thisZipFile.status.push("WARNING: this file does not appear to be a zip file"); + } else { + thisZipFile.binaryStream.seek(0, JSIO.SeekOrigin.Begin); + ReadZipEntries(); + if (thisZipFile.verbose > 0) { + thisZipFile.status.push("INFO: read " + thisZipFile.entries.length + " entries"); + } + } + } + else { + thisZipFile.status.push("ERROR: the URL could not be read (" + + bfr.req.status + " " + bfr.req.statusText + ")"); + } + callback(thisZipFile); + } + catch (exc1) + { + thisZipFile.status.push("EXCEPTION: " + exc1.message); + callback(thisZipFile); + } + }; + + this.binaryStream = new JSIO.BinaryUrlStream(fileUrl, parseZipFile); + + return this; + }; + + + ZipFile.Signatures = { + Entry : 0x04034b50, + EndOfCentralDirectory : 0x06054b50, + DirEntry : 0x02014b50, + DataDescriptor : 0x08074b50 + }; + + ZipFile.Version = version; + + ZipFile.EncryptionAlgorithm = { + None : 0, + PkzipWeak : 1, + WinZipAes : 2 + }; + + ZipFile.ExtraFieldTypes = {}; + ZipFile.ExtraFieldTypes[0x0001] = 'Zip64 Extended Info'; + ZipFile.ExtraFieldTypes[0x0007] = 'AV Info'; + ZipFile.ExtraFieldTypes[0x0008] = 'Extended Language Encoding Data (PFS)'; + ZipFile.ExtraFieldTypes[0x0009] = 'OS/2'; + ZipFile.ExtraFieldTypes[0x000a] = 'NTFS '; + ZipFile.ExtraFieldTypes[0x000c] = 'OpenVMS'; + ZipFile.ExtraFieldTypes[0x000d] = 'UNIX'; + ZipFile.ExtraFieldTypes[0x000e] = 'File Stream and Fork Descriptors'; + ZipFile.ExtraFieldTypes[0x000f] = 'Patch Descriptor'; + ZipFile.ExtraFieldTypes[0x0014] = 'PKCS#7 Store for X.509 Certificates'; + ZipFile.ExtraFieldTypes[0x0015] = 'X.509 Certificate ID and Signature (Individual File)'; + ZipFile.ExtraFieldTypes[0x0016] = 'X.509 Certificate ID (Central Directory)'; + ZipFile.ExtraFieldTypes[0x0017] = 'Strong Encryption Header'; + ZipFile.ExtraFieldTypes[0x0018] = 'Record Management Controls'; + ZipFile.ExtraFieldTypes[0x0019] = 'PKCS#7 Encryption Recipient Certificate List'; + ZipFile.ExtraFieldTypes[0x0065] = 'IBM S/390 (Z390), AS/400 (I400) attributes (uncompressed)'; + ZipFile.ExtraFieldTypes[0x0066] = 'IBM S/390 (Z390), AS/400 (I400) attributes (compressed)'; + ZipFile.ExtraFieldTypes[0x4690] = 'POSZIP 4690 (reserved) '; + ZipFile.ExtraFieldTypes[0x07c8] = 'Macintosh'; + ZipFile.ExtraFieldTypes[0x2605] = 'ZipIt Macintosh'; + ZipFile.ExtraFieldTypes[0x2705] = 'ZipIt Macintosh 1.3.5+'; + ZipFile.ExtraFieldTypes[0x2805] = 'ZipIt Macintosh 1.3.5+'; + ZipFile.ExtraFieldTypes[0x334d] = 'Info-ZIP Macintosh'; + ZipFile.ExtraFieldTypes[0x4341] = 'Acorn/SparkFS '; + ZipFile.ExtraFieldTypes[0x4453] = 'Windows NT security descriptor (binary ACL)'; + ZipFile.ExtraFieldTypes[0x4704] = 'VM/CMS'; + ZipFile.ExtraFieldTypes[0x470f] = 'MVS'; + ZipFile.ExtraFieldTypes[0x4b46] = 'FWKCS MD5'; + ZipFile.ExtraFieldTypes[0x4c41] = 'OS/2 access control list (text ACL)'; + ZipFile.ExtraFieldTypes[0x4d49] = 'Info-ZIP OpenVMS'; + ZipFile.ExtraFieldTypes[0x4f4c] = 'Xceed original location extra field'; + ZipFile.ExtraFieldTypes[0x5356] = 'AOS/VS (ACL)'; + ZipFile.ExtraFieldTypes[0x5455] = 'extended timestamp'; + ZipFile.ExtraFieldTypes[0x554e] = 'Xceed unicode extra field'; + ZipFile.ExtraFieldTypes[0x5855] = 'Info-ZIP UNIX (original, also OS/2, NT, etc)'; + ZipFile.ExtraFieldTypes[0x6375] = 'Info-ZIP Unicode Comment Extra Field'; + ZipFile.ExtraFieldTypes[0x6542] = 'BeOS/BeBox'; + ZipFile.ExtraFieldTypes[0x7075] = 'Info-ZIP Unicode Path Extra Field'; + ZipFile.ExtraFieldTypes[0x756e] = 'ASi UNIX'; + ZipFile.ExtraFieldTypes[0x7855] = 'Info-ZIP UNIX (new)'; + ZipFile.ExtraFieldTypes[0xa220] = 'Microsoft Open Packaging Growth Hint'; + ZipFile.ExtraFieldTypes[0xfd4a] = 'SMS/QDOS'; })(); diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/geoxml3.js b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/geoxml3.js index 6cc72d79..83d5929c 100644 --- a/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/geoxml3.js +++ b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/geoxml3.js @@ -24,17 +24,67 @@ * limitations under the License. * */ - -if (!String.prototype.trim) { /** - * Remove leading and trailing whitespace. + * A MultiGeometry object that will allow multiple polylines in a MultiGeometry + * containing LineStrings to be treated as a single object * - * @augments String - * @return {String} + * @param {MutiGeometryOptions} anonymous object. Available properties: + * map: The map on which to attach the MultiGeometry + * paths: the individual polylines + * polylineOptions: options to use when constructing all the polylines + * + * @constructor */ - String.prototype.trim = function () { - return this.replace(/^\s+|\s+$/g, ''); - }; +// only if Google Maps API included +if (!!window.google && !! google.maps) { + function MultiGeometry(multiGeometryOptions) { + function createPolyline(polylineOptions, mg) { + var polyline = new google.maps.Polyline(polylineOptions); + google.maps.event.addListener(polyline,'click', function(evt) { google.maps.event.trigger(mg,'click',evt);}); + google.maps.event.addListener(polyline,'dblclick', function(evt) { google.maps.event.trigger(mg, 'dblclick', evt);}); + google.maps.event.addListener(polyline,'mousedown', function(evt) { google.maps.event.trigger(mg, 'mousedown', evt);}); + google.maps.event.addListener(polyline,'mousemove', function(evt) { google.maps.event.trigger(mg, 'mousemove', evt);}); + google.maps.event.addListener(polyline,'mouseout', function(evt) { google.maps.event.trigger(mg, 'mouseout', evt);}); + google.maps.event.addListener(polyline,'mouseover', function(evt) { google.maps.event.trigger(mg, 'mouseover', evt);}); + google.maps.event.addListener(polyline,'mouseup', function(evt) { google.maps.event.trigger(mg, 'mouseup', evt);}); + google.maps.event.addListener(polyline,'rightclick', function(evt) { google.maps.event.trigger(mg, 'rightclick', evt);}); + return polyline; + } + this.setValues(multiGeometryOptions); + this.polylines = []; + + for (i=0; i<this.paths.length;i++) { + var polylineOptions = multiGeometryOptions; + polylineOptions.path = this.paths[i]; + var polyline = createPolyline(polylineOptions,this); + // Bind the polyline properties to the MultiGeometry properties + this.polylines.push(polyline); + } + } + MultiGeometry.prototype = new google.maps.MVCObject(); + MultiGeometry.prototype.changed = function(key) { + // alert(key+" changed"); + if (this.polylines) { + for (var i=0; i<this.polylines.length; i++) { + this.polylines[i].set(key,this.get(key)); + } + } + }; + MultiGeometry.prototype.setMap = function(map) { this.set('map',map); }; + MultiGeometry.prototype.getMap = function() { return this.get('map'); }; +} + +// Extend the global String object with a method to remove leading and trailing whitespace +if (!String.prototype.trim) { + /** + * Remove leading and trailing whitespace. + * + * @augments String + * @return {String} + */ + String.prototype.trim = function () { + return this.replace(/^\s+|\s+$/g, ''); + }; } /** @@ -53,977 +103,1049 @@ geoXML3 = window.geoXML3 || {instances: []}; * @param {geoXML3.parserOptions} options */ geoXML3.parser = function (options) { - // Private variables - var parserOptions = new geoXML3.parserOptions(options); - var docs = []; // Individual KML documents - var docsByUrl = {}; // Same docs as an hash by cleanURL - var kmzMetaData = {}; // Extra files from KMZ data - var styles = {}; // Global list of styles - var lastPlacemark; - var parserName; - if (!parserOptions.infoWindow && parserOptions.singleInfoWindow) - parserOptions.infoWindow = new google.maps.InfoWindow(); - - var parseKmlString = function (kmlString, docSet) { - // Internal values for the set of documents as a whole - var internals = { - parser: this, - docSet: docSet || [], - remaining: 1, - parseOnly: !(parserOptions.afterParse || parserOptions.processStyles) - }; - thisDoc = new Object(); - thisDoc.internals = internals; - internals.docSet.push(thisDoc); - render(geoXML3.xmlParse(kmlString),thisDoc); - } - - var parse = function (urls, docSet) { - // Process one or more KML documents - if (!parserName) { - parserName = 'geoXML3.instances[' + (geoXML3.instances.push(this) - 1) + ']'; - } - - if (typeof urls === 'string') { - // Single KML document - urls = [urls]; - } - - // Internal values for the set of documents as a whole - var internals = { - parser: this, - docSet: docSet || [], - remaining: urls.length, - parseOnly: !(parserOptions.afterParse || parserOptions.processStyles) - }; - var thisDoc, j; - for (var i = 0; i < urls.length; i++) { - var baseUrl = cleanURL(defileURL(location.pathname), urls[i]); - if (docsByUrl[baseUrl]) { - // Reloading an existing document - thisDoc = docsByUrl[baseUrl]; - thisDoc.reload = true; - } - else { - thisDoc = new Object(); - thisDoc.baseUrl = baseUrl; - internals.docSet.push(thisDoc); - } - thisDoc.url = urls[i]; - thisDoc.internals = internals; - fetchDoc(thisDoc.url, thisDoc); - } - }; - - function fetchDoc(url, doc, resFunc) { - resFunc = resFunc || function (responseXML) { render(responseXML, doc); }; - - if (typeof ZipFile === 'function' && typeof JSIO === 'object' && typeof JSIO.guessFileType === 'function') { // KMZ support requires these modules loaded - contentType = JSIO.guessFileType(doc.baseUrl); - if (contentType == JSIO.FileType.Binary || contentType == JSIO.FileType.Unknown) { - doc.isCompressed = true; - doc.baseDir = doc.baseUrl + '/'; - geoXML3.fetchZIP(url, resFunc, doc.internals.parser); - return; - } - } - doc.isCompressed = false; - doc.baseDir = defileURL(doc.baseUrl); - geoXML3.fetchXML(url, resFunc); - } - - var hideDocument = function (doc) { - if (!doc) doc = docs[0]; - // Hide the map objects associated with a document - var i; - if (!!doc.markers) { - for (i = 0; i < doc.markers.length; i++) { - if(!!doc.markers[i].infoWindow) doc.markers[i].infoWindow.close(); - doc.markers[i].setVisible(false); - } - } - if (!!doc.ggroundoverlays) { - for (i = 0; i < doc.ggroundoverlays.length; i++) { - doc.ggroundoverlays[i].setOpacity(0); - } - } - if (!!doc.gpolylines) { - for (i=0;i<doc.gpolylines.length;i++) { - if(!!doc.gpolylines[i].infoWindow) doc.gpolylines[i].infoWindow.close(); - doc.gpolylines[i].setMap(null); - } - } - if (!!doc.gpolygons) { - for (i=0;i<doc.gpolygons.length;i++) { - if(!!doc.gpolygons[i].infoWindow) doc.gpolygons[i].infoWindow.close(); - doc.gpolygons[i].setMap(null); - } - } - }; - - var showDocument = function (doc) { - if (!doc) doc = docs[0]; - // Show the map objects associated with a document - var i; - if (!!doc.markers) { - for (i = 0; i < doc.markers.length; i++) { - doc.markers[i].setVisible(true); - } - } - if (!!doc.ggroundoverlays) { - for (i = 0; i < doc.ggroundoverlays.length; i++) { - doc.ggroundoverlays[i].setOpacity(doc.ggroundoverlays[i].percentOpacity_); - } - } - if (!!doc.gpolylines) { - for (i=0;i<doc.gpolylines.length;i++) { - doc.gpolylines[i].setMap(parserOptions.map); - } - } - if (!!doc.gpolygons) { - for (i=0;i<doc.gpolygons.length;i++) { - doc.gpolygons[i].setMap(parserOptions.map); - } - } - }; - - var defaultStyle = { - balloon: { - bgColor: 'ffffffff', - textColor: 'ff000000', - text: "<h3>$[name]</h3>\n<div>$[description]</div>\n<div>$[geDirections]</div>", - displayMode: 'default' - }, - icon: { - scale: 1.0, - dim: { - x: 0, - y: 0, - w: -1, - h: -1 - }, - hotSpot: { - x: 0.5, - y: 0.5, - xunits: 'fraction', - yunits: 'fraction' - } - }, - line: { - color: 'ffffffff', // white (KML default) - colorMode: 'normal', - width: 1.0 - }, - poly: { - color: 'ffffffff', // white (KML default) - colorMode: 'normal', - fill: true, - outline: true - } - }; - - var kmlNS = 'http://www.opengis.net/kml/2.2'; - var gxNS = 'http://www.google.com/kml/ext/2.2'; - var nodeValue = geoXML3.nodeValue; - var getBooleanValue = geoXML3.getBooleanValue; - var getElementsByTagNameNS = geoXML3.getElementsByTagNameNS; - var getElementsByTagName = geoXML3.getElementsByTagName; - -function processStyleUrl(node) { - var styleUrlStr = nodeValue(getElementsByTagName(node, 'styleUrl')[0]); - if (!!styleUrlStr && styleUrlStr.indexOf('#') != -1) - var styleUrl = styleUrlStr.split('#'); - else var styleUrl = ["",""]; - return styleUrl; -} - - function processStyle(thisNode, baseUrl, styleID, baseDir) { - var style = (baseUrl === '{inline}') ? clone(defaultStyle) : (styles[baseUrl][styleID] = styles[baseUrl][styleID] || clone(defaultStyle)); - - var styleNodes = getElementsByTagName(thisNode, 'BalloonStyle'); - if (!!styleNodes && styleNodes.length > 0) { - style.balloon.bgColor = nodeValue(getElementsByTagName(styleNodes[0], 'bgColor')[0], style.balloon.bgColor); - style.balloon.textColor = nodeValue(getElementsByTagName(styleNodes[0], 'textColor')[0], style.balloon.textColor); - style.balloon.text = nodeValue(getElementsByTagName(styleNodes[0], 'text')[0], style.balloon.text); - style.balloon.displayMode = nodeValue(getElementsByTagName(styleNodes[0], 'displayMode')[0], style.balloon.displayMode); - } - - // style.list = (unsupported; doesn't make sense in Google Maps) - - var styleNodes = getElementsByTagName(thisNode, 'IconStyle'); - if (!!styleNodes && styleNodes.length > 0) { - var icon = style.icon; - - icon.scale = parseFloat(nodeValue(getElementsByTagName(styleNodes[0], 'scale')[0], icon.scale)); - // style.icon.heading = (unsupported; not supported in API) - // style.icon.color = (unsupported; not supported in API) - // style.icon.colorMode = (unsupported; not supported in API) - - styleNodes = getElementsByTagName(thisNode, 'Icon'); - if (!!styleNodes && styleNodes.length > 0) { - icon.href = nodeValue(getElementsByTagName(styleNodes[0], 'href')[0]); - icon.url = cleanURL(baseDir, icon.href); - // Detect images buried in KMZ files (and use a base64 encoded URL) - if (kmzMetaData[icon.url]) icon.url = kmzMetaData[icon.url].dataUrl; - - // Support for icon palettes and exact size dimensions - icon.dim = { - x: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'x')[0], icon.dim.x)), - y: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'y')[0], icon.dim.y)), - w: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'w')[0], icon.dim.w)), - h: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'h')[0], icon.dim.h)) - }; - - styleNodes = getElementsByTagName(styleNodes[0], 'hotSpot')[0]; - if (!!styleNodes && styleNodes.length > 0) { - icon.hotSpot = { - x: styleNodes[0].getAttribute('x'), - y: styleNodes[0].getAttribute('y'), - xunits: styleNodes[0].getAttribute('xunits'), - yunits: styleNodes[0].getAttribute('yunits') - }; - } - - // certain occasions where we need the pixel size of the image (like the default settings...) - // (NOTE: Scale is applied to entire image, not just the section of the icon palette. So, - // if we need scaling, we'll need the img dimensions no matter what.) - if ( (icon.dim.w < 0 || icon.dim.h < 0) && (icon.xunits != 'pixels' || icon.yunits == 'fraction') || icon.scale != 1.0) { - // (hopefully, this will load by the time we need it...) - icon.img = new Image(); - icon.img.onload = function() { - if (icon.dim.w < 0 || icon.dim.h < 0) { - icon.dim.w = this.width; - icon.dim.h = this.height; - } - }; - icon.img.src = icon.url; - - // sometimes the file is already cached and it never calls onLoad - if (icon.img.width > 0) { - icon.dim.w = icon.img.width; - icon.dim.h = icon.img.height; - } - } - } - } - - // style.label = (unsupported; may be possible but not with API) - - styleNodes = getElementsByTagName(thisNode, 'LineStyle'); - if (!!styleNodes && styleNodes.length > 0) { - style.line.color = nodeValue(getElementsByTagName(styleNodes[0], 'color')[0], style.line.color); - style.line.colorMode = nodeValue(getElementsByTagName(styleNodes[0], 'colorMode')[0], style.line.colorMode); - style.line.width = nodeValue(getElementsByTagName(styleNodes[0], 'width')[0], style.line.width); - // style.line.outerColor = (unsupported; not supported in API) - // style.line.outerWidth = (unsupported; not supported in API) - // style.line.physicalWidth = (unsupported; unnecessary in Google Maps) - // style.line.labelVisibility = (unsupported; possible to implement) - } - - styleNodes = getElementsByTagName(thisNode, 'PolyStyle'); - if (!!styleNodes && styleNodes.length > 0) { - style.poly.color = nodeValue( getElementsByTagName(styleNodes[0], 'color')[0], style.poly.color); - style.poly.colorMode = nodeValue( getElementsByTagName(styleNodes[0], 'colorMode')[0], style.poly.colorMode); - style.poly.outline = getBooleanValue(getElementsByTagName(styleNodes[0], 'outline')[0], style.poly.outline); - style.poly.fill = getBooleanValue(getElementsByTagName(styleNodes[0], 'fill')[0], style.poly.fill); - } - return style; - } - - // from http://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-clone-a-javascript-object - // http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone - function clone(obj){ - if(obj == null || typeof(obj) != 'object') return obj; - if (obj.cloneNode) return obj.cloneNode(true); - var temp = new obj.constructor(); - for(var key in obj) temp[key] = clone(obj[key]); - return temp; - } - - function processStyleMap(thisNode, baseUrl, styleID, baseDir) { - var pairs = getElementsByTagName(thisNode, 'Pair'); - var map = new Object(); - - // add each key to the map - for (var pr=0;pr<pairs.length;pr++) { - var pairKey = nodeValue(getElementsByTagName(pairs[pr], 'key')[0]); - var pairStyle = nodeValue(getElementsByTagName(pairs[pr], 'Style')[0]); - var pairStyleUrl = processStyleUrl(pairs[pr]); - var pairStyleBaseUrl = pairStyleUrl[0] ? cleanURL(baseDir, pairStyleUrl[0]) : baseUrl; - var pairStyleID = pairStyleUrl[1]; - - if (!!pairStyle) { - map[pairKey] = processStyle(pairStyle, pairStyleBaseUrl, pairStyleID); - } else if (!!pairStyleID && !!styles[pairStyleBaseUrl][pairStyleID]) { - map[pairKey] = clone(styles[pairStyleBaseUrl][pairStyleID]); - } - } - if (!!map["normal"]) { - styles[baseUrl][styleID] = clone(map["normal"]); - } else { - styles[baseUrl][styleID] = clone(defaultStyle); - } - if (!!map["highlight"]) { - processStyleID(map["highlight"]); - } - styles[baseUrl][styleID].map = clone(map); - } - - function processPlacemarkCoords(node, tag) { - var parent = getElementsByTagName(node, tag); - var coordListA = []; - for (var i=0; i<parent.length; i++) { - var coordNodes = getElementsByTagName(parent[i], 'coordinates'); - if (!coordNodes) { - if (coordListA.length > 0) { - break; - } else { - return [{coordinates: []}]; - } - } - - for (var j=0; j<coordNodes.length;j++) { - var coords = nodeValue(coordNodes[j]).trim(); - coords = coords.replace(/,\s+/g, ','); - var path = coords.split(/\s+/g); - var pathLength = path.length; - var coordList = []; - for (var k = 0; k < pathLength; k++) { - coords = path[k].split(','); - if (!isNaN(coords[0]) && !isNaN(coords[1])) { - coordList.push({ - lat: parseFloat(coords[1]), - lng: parseFloat(coords[0]), - alt: parseFloat(coords[2]) - }); - } - } - coordListA.push({coordinates: coordList}); - } - } - return coordListA; - } - - var render = function (responseXML, doc) { - // Callback for retrieving a KML document: parse the KML and display it on the map - if (!responseXML) { - // Error retrieving the data - geoXML3.log('Unable to retrieve ' + doc.url); - if (parserOptions.failedParse) parserOptions.failedParse(doc); - doc.failed = true; - return; - } else if (responseXML.parseError && responseXML.parseError.errorCode != 0) { - // IE parse error - var err = responseXML.parseError; - var msg = 'Parse error in line ' + err.line + ', col ' + err.linePos + ' (error code: ' + err.errorCode + ")\n" + - "\nError Reason: " + err.reason + - 'Error Line: ' + err.srcText; - - geoXML3.log('Unable to retrieve ' + doc.url + ': ' + msg); - if (parserOptions.failedParse) parserOptions.failedParse(doc); - doc.failed = true; - return; - } else if (responseXML.documentElement && responseXML.documentElement.nodeName == 'parsererror') { - // Firefox parse error - geoXML3.log('Unable to retrieve ' + doc.url + ': ' + responseXML.documentElement.childNodes[0].nodeValue); - if (parserOptions.failedParse) parserOptions.failedParse(doc); - doc.failed = true; - return; - } else if (!doc) { - throw 'geoXML3 internal error: render called with null document'; - } else { //no errors - var i; - doc.placemarks = []; - doc.groundoverlays = []; - doc.ggroundoverlays = []; - doc.networkLinks = []; - doc.gpolygons = []; - doc.gpolylines = []; - - // Check for dependent KML files - var nodes = getElementsByTagName(responseXML, 'styleUrl'); - var docSet = doc.internals.docSet; - - for (var i = 0; i < nodes.length; i++) { - var url = nodeValue(nodes[i]).split('#')[0]; - if (!url) continue; // #id (inside doc) - var rUrl = cleanURL( doc.baseDir, url ); - if (rUrl === doc.baseUrl) continue; // self - if (docsByUrl[rUrl]) continue; // already loaded - - var thisDoc; - var j = docSet.indexOfObjWithItem('baseUrl', rUrl); - if (j != -1) { - // Already listed to be loaded, but probably in the wrong order. - // Load it right away to immediately resolve dependency. - thisDoc = docSet[j]; - if (thisDoc.failed) continue; // failed to load last time; don't retry it again - } - else { - // Not listed at all; add it in - thisDoc = new Object(); - thisDoc.url = rUrl; // url can't be trusted inside KMZ files, since it may .. outside of the archive - thisDoc.baseUrl = rUrl; - thisDoc.internals = doc.internals; - - doc.internals.docSet.push(thisDoc); - doc.internals.remaining++; - } - - // render dependent KML first then re-run renderer - fetchDoc(rUrl, thisDoc, function (thisResXML) { - render(thisResXML, thisDoc); - render(responseXML, doc); - }); - - // to prevent cross-dependency issues, just load the one - // file first and re-check the rest later - return; - } - - // Parse styles - doc.styles = styles[doc.baseUrl] = styles[doc.baseUrl] || {}; - var styleID, styleNodes; - nodes = getElementsByTagName(responseXML, 'Style'); - nodeCount = nodes.length; - for (i = 0; i < nodeCount; i++) { - thisNode = nodes[i]; - var styleID = thisNode.getAttribute('id'); - if (!!styleID) processStyle(thisNode, doc.baseUrl, styleID, doc.baseDir); - } - // Parse StyleMap nodes - nodes = getElementsByTagName(responseXML, 'StyleMap'); - for (i = 0; i < nodes.length; i++) { - thisNode = nodes[i]; - var styleID = thisNode.getAttribute('id'); - if (!!styleID) processStyleMap(thisNode, doc.baseUrl, styleID, doc.baseDir); - } - - if (!!parserOptions.processStyles || !parserOptions.createMarker) { - // Convert parsed styles into GMaps equivalents - processStyles(doc); - } - - // Parse placemarks - if (!!doc.reload && !!doc.markers) { - for (i = 0; i < doc.markers.length; i++) { - doc.markers[i].active = false; - } - } - var placemark, node, coords, path, marker, poly; - var placemark, coords, path, pathLength, marker, polygonNodes, coordList; - var placemarkNodes = getElementsByTagName(responseXML, 'Placemark'); - for (pm = 0; pm < placemarkNodes.length; pm++) { - // Init the placemark object - node = placemarkNodes[pm]; - var styleUrl = processStyleUrl(node); - placemark = { - name: nodeValue(getElementsByTagName(node, 'name')[0]), - description: nodeValue(getElementsByTagName(node, 'description')[0]), - styleUrl: styleUrl.join('#'), - styleBaseUrl: styleUrl[0] ? cleanURL(doc.baseDir, styleUrl[0]) : doc.baseUrl, - styleID: styleUrl[1], - visibility: getBooleanValue(getElementsByTagName(node, 'visibility')[0], true), - balloonVisibility: getBooleanValue(getElementsByTagNameNS(node, gxNS, 'balloonVisibility')[0], !parserOptions.suppressInfoWindows) - }; - placemark.style = (styles[placemark.styleBaseUrl] && styles[placemark.styleBaseUrl][placemark.styleID]) || clone(defaultStyle); - // inline style overrides shared style - var inlineStyles = getElementsByTagName(node, 'Style'); - if (inlineStyles && (inlineStyles.length > 0)) { - var style = processStyle(node, '{inline}', '{inline}'); - processStyleID(style); - if (style) placemark.style = style; - } - - if (/^https?:\/\//.test(placemark.description)) { - placemark.description = ['<a href="', placemark.description, '">', placemark.description, '</a>'].join(''); - } - - // record list of variables for substitution - placemark.vars = { - display: { - name: 'Name', - description: 'Description', - address: 'Street Address', - id: 'ID', - Snippet: 'Snippet', - geDirections: 'Directions' - }, - val: { - name: placemark.name || '', - description: placemark.description || '', - address: nodeValue(getElementsByTagName(node, 'address')[0], ''), - id: node.getAttribute('id') || '', - Snippet: nodeValue(getElementsByTagName(node, 'Snippet')[0], '') - }, - directions: [ - 'f=d', - 'source=GeoXML3' - ] - }; - - // add extended data to variables - var extDataNodes = getElementsByTagName(node, 'ExtendedData'); - if (!!extDataNodes && extDataNodes.length > 0) { - var dataNodes = getElementsByTagName(extDataNodes[0], 'Data'); - for (var d = 0; d < dataNodes.length; d++) { - var dn = dataNodes[d]; - var name = dn.getAttribute('name'); - if (!name) continue; - var dName = nodeValue(getElementsByTagName(dn, 'displayName')[0], name); - var val = nodeValue(getElementsByTagName(dn, 'value')[0]); - - placemark.vars.val[name] = val; - placemark.vars.display[name] = dName; - } - } - - // process MultiGeometry - var GeometryNodes = getElementsByTagName(node, 'coordinates'); - var Geometry = null; - if (!!GeometryNodes && (GeometryNodes.length > 0)) { - for (var gn=0;gn<GeometryNodes.length;gn++) { - if (GeometryNodes[gn].parentNode && - GeometryNodes[gn].parentNode.nodeName) { - var GeometryPN = GeometryNodes[gn].parentNode; - Geometry = GeometryPN.nodeName; - - // Extract the coordinates - // What sort of placemark? - switch(Geometry) { - case "Point": - placemark.Point = processPlacemarkCoords(node, "Point")[0]; - placemark.latlng = new google.maps.LatLng(placemark.Point.coordinates[0].lat, placemark.Point.coordinates[0].lng); - pathLength = 1; - break; - case "LinearRing": - // Polygon/line - polygonNodes = getElementsByTagName(node, 'Polygon'); - // Polygon - if (!placemark.Polygon) - placemark.Polygon = [{ - outerBoundaryIs: {coordinates: []}, - innerBoundaryIs: [{coordinates: []}] - }]; - for (var pg=0;pg<polygonNodes.length;pg++) { - placemark.Polygon[pg] = { - outerBoundaryIs: {coordinates: []}, - innerBoundaryIs: [{coordinates: []}] - } - placemark.Polygon[pg].outerBoundaryIs = processPlacemarkCoords(polygonNodes[pg], "outerBoundaryIs"); - placemark.Polygon[pg].innerBoundaryIs = processPlacemarkCoords(polygonNodes[pg], "innerBoundaryIs"); - } - coordList = placemark.Polygon[0].outerBoundaryIs; - break; - - case "LineString": - pathLength = 0; - placemark.LineString = processPlacemarkCoords(node,"LineString"); - break; - - default: - break; - } - } - } - } - - // call the custom placemark parse function if it is defined - if (!!parserOptions.pmParseFn) parserOptions.pmParseFn(node, placemark); - doc.placemarks.push(placemark); - - // single marker - if (placemark.Point) { - if (!!google.maps) { - doc.bounds = doc.bounds || new google.maps.LatLngBounds(); - doc.bounds.extend(placemark.latlng); - } - - // Potential user-defined marker handler - var pointCreateFunc = parserOptions.createMarker || createMarker; - var found = false; - if (!parserOptions.createMarker) { - // Check to see if this marker was created on a previous load of this document - if (!!doc) { - doc.markers = doc.markers || []; - if (doc.reload) { - for (var j = 0; j < doc.markers.length; j++) { - if (doc.markers[j].getPosition().equals(placemark.latlng)) { - found = doc.markers[j].active = true; - break; - } - } - } - } - } - if (!found) { - // Call the marker creator - var marker = pointCreateFunc(placemark, doc); - if (marker) marker.active = placemark.visibility; - } - } - // polygon/line - var poly, line; - if (!!doc) { - if (placemark.Polygon) doc.gpolygons = doc.gpolygons || []; - if (placemark.LineString) doc.gpolylines = doc.gpolylines || []; - } - - var polyCreateFunc = parserOptions.createPolygon || createPolygon; - var lineCreateFunc = parserOptions.createLineString || createPolyline; - if (placemark.Polygon) { - poly = polyCreateFunc(placemark,doc); - if (poly) poly.active = placemark.visibility; - } - if (placemark.LineString) { - line = lineCreateFunc(placemark,doc); - if (line) line.active = placemark.visibility; - } - if (!!google.maps) { - doc.bounds = doc.bounds || new google.maps.LatLngBounds(); - if (poly) doc.bounds.union(poly.bounds); - if (line) doc.bounds.union(line.bounds); - } - - } // placemark loop - - if (!!doc.reload && !!doc.markers) { - for (i = doc.markers.length - 1; i >= 0 ; i--) { - if (!doc.markers[i].active) { - if (!!doc.markers[i].infoWindow) { - doc.markers[i].infoWindow.close(); - } - doc.markers[i].setMap(null); - doc.markers.splice(i, 1); - } - } - } - - // Parse ground overlays - if (!!doc.reload && !!doc.groundoverlays) { - for (i = 0; i < doc.groundoverlays.length; i++) { - doc.groundoverlays[i].active = false; - } - } - - if (!!doc) { - doc.groundoverlays = doc.groundoverlays || []; - } - // doc.groundoverlays =[]; - var groundOverlay, color, transparency, overlay; - var groundNodes = getElementsByTagName(responseXML, 'GroundOverlay'); - for (i = 0; i < groundNodes.length; i++) { - node = groundNodes[i]; - - // Detect images buried in KMZ files (and use a base64 encoded URL) - var gnUrl = cleanURL( doc.baseDir, nodeValue(getElementsByTagName(node, 'href')[0]) ); - if (kmzMetaData[gnUrl]) gnUrl = kmzMetaData[gnUrl].dataUrl; - - // Init the ground overlay object - groundOverlay = { - name: nodeValue(getElementsByTagName(node, 'name')[0]), - description: nodeValue(getElementsByTagName(node, 'description')[0]), - icon: { href: gnUrl }, - latLonBox: { - north: parseFloat(nodeValue(getElementsByTagName(node, 'north')[0])), - east: parseFloat(nodeValue(getElementsByTagName(node, 'east')[0])), - south: parseFloat(nodeValue(getElementsByTagName(node, 'south')[0])), - west: parseFloat(nodeValue(getElementsByTagName(node, 'west')[0])) - } - }; - if (!!google.maps) { - doc.bounds = doc.bounds || new google.maps.LatLngBounds(); - doc.bounds.union(new google.maps.LatLngBounds( - new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west), - new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east) - )); - } - - // Opacity is encoded in the color node - var colorNode = getElementsByTagName(node, 'color'); - if (colorNode && colorNode.length > 0) { - groundOverlay.opacity = geoXML3.getOpacity(nodeValue(colorNode[0])); - } else { - groundOverlay.opacity = 1.0; // KML default - } - - doc.groundoverlays.push(groundOverlay); - if (!!parserOptions.createOverlay) { - // User-defined overlay handler - parserOptions.createOverlay(groundOverlay, doc); - } else { - // Check to see if this overlay was created on a previous load of this document - var found = false; - if (!!doc) { - doc.groundoverlays = doc.groundoverlays || []; - if (doc.reload) { - overlayBounds = new google.maps.LatLngBounds( - new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west), - new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east) - ); - var overlays = doc.groundoverlays; - for (i = overlays.length; i--;) { - if ((overlays[i].bounds().equals(overlayBounds)) && - (overlays.url_ === groundOverlay.icon.href)) { - found = overlays[i].active = true; - break; - } - } - } - } - - if (!found) { - // Call the built-in overlay creator - overlay = createOverlay(groundOverlay, doc); - overlay.active = true; - } - } - if (!!doc.reload && !!doc.groundoverlays && !!doc.groundoverlays.length) { - var overlays = doc.groundoverlays; - for (i = overlays.length; i--;) { - if (!overlays[i].active) { - overlays[i].remove(); - overlays.splice(i, 1); - } - } - doc.groundoverlays = overlays; - } - } - - // Parse network links - var networkLink; - var docPath = document.location.pathname.split('/'); - docPath = docPath.splice(0, docPath.length - 1).join('/'); - var linkNodes = getElementsByTagName(responseXML, 'NetworkLink'); - for (i = 0; i < linkNodes.length; i++) { - node = linkNodes[i]; - - // Init the network link object - networkLink = { - name: nodeValue(getElementsByTagName(node, 'name')[0]), - link: { - href: nodeValue(getElementsByTagName(node, 'href')[0]), - refreshMode: nodeValue(getElementsByTagName(node, 'refreshMode')[0]) - } - }; - - // Establish the specific refresh mode - if (!networkLink.link.refreshMode) { - networkLink.link.refreshMode = 'onChange'; - } - if (networkLink.link.refreshMode === 'onInterval') { - networkLink.link.refreshInterval = parseFloat(nodeValue(getElementsByTagName(node, 'refreshInterval')[0])); - if (isNaN(networkLink.link.refreshInterval)) { - networkLink.link.refreshInterval = 0; - } - } else if (networkLink.link.refreshMode === 'onChange') { - networkLink.link.viewRefreshMode = nodeValue(getElementsByTagName(node, 'viewRefreshMode')[0]); - if (!networkLink.link.viewRefreshMode) { - networkLink.link.viewRefreshMode = 'never'; - } - if (networkLink.link.viewRefreshMode === 'onStop') { - networkLink.link.viewRefreshTime = nodeValue(getElementsByTagName(node, 'refreshMode')[0]); - networkLink.link.viewFormat = nodeValue(getElementsByTagName(node, 'refreshMode')[0]); - if (!networkLink.link.viewFormat) { - networkLink.link.viewFormat = 'BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth]'; - } - } - } - - if (!/^[\/|http]/.test(networkLink.link.href)) { - // Fully-qualify the HREF - networkLink.link.href = docPath + '/' + networkLink.link.href; - } - - // Apply the link - if ((networkLink.link.refreshMode === 'onInterval') && - (networkLink.link.refreshInterval > 0)) { - // Reload at regular intervals - setInterval(parserName + '.parse("' + networkLink.link.href + '")', - 1000 * networkLink.link.refreshInterval); - } else if (networkLink.link.refreshMode === 'onChange') { - if (networkLink.link.viewRefreshMode === 'never') { - // Load the link just once - doc.internals.parser.parse(networkLink.link.href, doc.internals.docSet); - } else if (networkLink.link.viewRefreshMode === 'onStop') { - // Reload when the map view changes - - } - } - } - } - - if (!!doc.bounds) { - doc.internals.bounds = doc.internals.bounds || new google.maps.LatLngBounds(); - doc.internals.bounds.union(doc.bounds); - } - if (!!doc.markers || !!doc.groundoverlays || !!doc.gpolylines || !!doc.gpolygons) { - doc.internals.parseOnly = false; - } - - if (!doc.internals.parseOnly) { - // geoXML3 is not being used only as a real-time parser, so keep the processed documents around - if (!docsByUrl[doc.baseUrl]) { - docs.push(doc); - docsByUrl[doc.baseUrl] = doc; - } - else { - // internal replacement, which keeps the same memory ref loc in docs and docsByUrl - for (var i in docsByUrl[doc.baseUrl]) { - docsByUrl[doc.baseUrl][i] = doc[i]; - } - } - } - - doc.internals.remaining--; - if (doc.internals.remaining === 0) { - // We're done processing this set of KML documents - // Options that get invoked after parsing completes - if (parserOptions.zoom && !!doc.internals.bounds) { - parserOptions.map.fitBounds(doc.internals.bounds); - } - if (parserOptions.afterParse) { - parserOptions.afterParse(doc.internals.docSet); - } - } - }; - - var kmlColor = function (kmlIn, colorMode) { - var kmlColor = {}; - kmlIn = kmlIn || 'ffffffff'; // white (KML 2.2 default) - - var aa = kmlIn.substr(0,2); - var bb = kmlIn.substr(2,2); - var gg = kmlIn.substr(4,2); - var rr = kmlIn.substr(6,2); - - kmlColor.opacity = parseInt(aa, 16) / 256; - kmlColor.color = (colorMode === 'random') ? randomColor(rr, gg, bb) : '#' + rr + gg + bb; - return kmlColor; - }; - - // Implemented per KML 2.2 <ColorStyle> specs - var randomColor = function(rr, gg, bb) { - var col = { rr: rr, gg: gg, bb: bb }; - for (var k in col) { - var v = col[k]; - if (v == null) v = 'ff'; - - // RGB values are limiters for random numbers (ie: 7f would be a random value between 0 and 7f) - v = Math.round(Math.random() * parseInt(rr, 16)).toString(16); - if (v.length === 1) v = '0' + v; - col[k] = v; - } - - return '#' + col.rr + col.gg + col.bb; - }; - - var processStyleID = function (style) { - var icon = style.icon; - if (!icon.href) return; - - if (icon.img && !icon.img.complete) { - // we're still waiting on the image loading (probably because we've been blocking since the declaration) - // so, let's queue this function on the onload stack - icon.markerBacklog = []; - icon.img.onload = function() { - if (icon.dim.w < 0 || icon.dim.h < 0) { - icon.dim.w = this.width; - icon.dim.h = this.height; - } - processStyleID(style); - - // we will undoubtedly get some createMarker queuing, so set this up in advance - for (var i = 0; i < icon.markerBacklog.length; i++) { - var p = icon.markerBacklog[i][0]; - var d = icon.markerBacklog[i][1]; - createMarker(p, d); - if (p.marker) p.marker.active = true; - } - delete icon.markerBacklog; - }; - return; - } - else if (icon.dim.w < 0 || icon.dim.h < 0) { - if (icon.img && icon.img.complete) { - // sometimes the file is already cached and it never calls onLoad - icon.dim.w = icon.img.width; - icon.dim.h = icon.img.height; - } - else { - // settle for a default of 32x32 - icon.dim.whGuess = true; - icon.dim.w = 32; - icon.dim.h = 32; - } - } - - // pre-scaled variables - var rnd = Math.round; - var scaled = { - x: icon.dim.x * icon.scale, - y: icon.dim.y * icon.scale, - w: icon.dim.w * icon.scale, - h: icon.dim.h * icon.scale, - aX: icon.hotSpot.x * icon.scale, - aY: icon.hotSpot.y * icon.scale, - iW: (icon.img ? icon.img.width : icon.dim.w) * icon.scale, - iH: (icon.img ? icon.img.height : icon.dim.h) * icon.scale - }; - - // Figure out the anchor spot - var aX, aY; - switch (icon.hotSpot.xunits) { - case 'fraction': aX = rnd(scaled.aX * icon.dim.w); break; - case 'insetPixels': aX = rnd(icon.dim.w * icon.scale - scaled.aX); break; - default: aX = rnd(scaled.aX); break; // already pixels - } - aY = rnd( ((icon.hotSpot.yunits === 'fraction') ? icon.dim.h : 1) * scaled.aY ); // insetPixels Y = pixels Y - var iconAnchor = new google.maps.Point(aX, aY); - - // Sizes - // (NOTE: Scale is applied to entire image, not just the section of the icon palette.) - var iconSize = icon.dim.whGuess ? null : new google.maps.Size(rnd(scaled.w), rnd(scaled.h)); - var iconScale = icon.scale == 1.0 ? null : - icon.dim.whGuess ? new google.maps.Size(rnd(scaled.w), rnd(scaled.h)) - : new google.maps.Size(rnd(scaled.iW), rnd(scaled.iH)); - var iconOrigin = new google.maps.Point(rnd(scaled.x), rnd(scaled.y)); - - // Detect images buried in KMZ files (and use a base64 encoded URL) - if (kmzMetaData[icon.url]) icon.url = kmzMetaData[icon.url].dataUrl; - - // Init the style object with the KML icon - icon.marker = new google.maps.MarkerImage( - icon.url, // url - iconSize, // size - iconOrigin, // origin - iconAnchor, // anchor - iconScale // scaledSize - ); - - // Look for a predictable shadow - var stdRegEx = /\/(red|blue|green|yellow|lightblue|purple|pink|orange)(-dot)?\.png/; - var shadowSize = new google.maps.Size(59, 32); - var shadowPoint = new google.maps.Point(16, 32); - if (stdRegEx.test(icon.href)) { - // A standard GMap-style marker icon - icon.shadow = new google.maps.MarkerImage( - 'http://maps.google.com/mapfiles/ms/micons/msmarker.shadow.png', // url - shadowSize, // size - null, // origin - shadowPoint, // anchor - shadowSize // scaledSize - ); - } else if (icon.href.indexOf('-pushpin.png') > -1) { - // Pushpin marker icon - icon.shadow = new google.maps.MarkerImage( - 'http://maps.google.com/mapfiles/ms/micons/pushpin_shadow.png', // url - shadowSize, // size - null, // origin - shadowPoint, // anchor - shadowSize // scaledSize - ); - } /* else { + // Inherit from Google MVC Object to include event handling + google.maps.MVCObject.call(this); + + // Private variables + var parserOptions = new geoXML3.parserOptions(options); + var docs = []; // Individual KML documents + var docsByUrl = {}; // Same docs as an hash by cleanURL + var kmzMetaData = {}; // Extra files from KMZ data + var styles = {}; // Global list of styles + var lastPlacemark; + var parserName; + if (!parserOptions.infoWindow && parserOptions.singleInfoWindow) + parserOptions.infoWindow = new google.maps.InfoWindow(); + + var parseKmlString = function (kmlString, docSet) { + // Internal values for the set of documents as a whole + var internals = { + parser: this, + docSet: docSet || [], + remaining: 1, + parseOnly: !(parserOptions.afterParse || parserOptions.processStyles) + }; + thisDoc = new Object(); + thisDoc.internals = internals; + internals.docSet.push(thisDoc); + render(geoXML3.xmlParse(kmlString),thisDoc); + } + + var parse = function (urls, docSet) { + // Process one or more KML documents + if (!parserName) { + parserName = 'geoXML3.instances[' + (geoXML3.instances.push(this) - 1) + ']'; + } + + if (typeof urls === 'string') { + // Single KML document + urls = [urls]; + } + + // Internal values for the set of documents as a whole + var internals = { + parser: this, + docSet: docSet || [], + remaining: urls.length, + parseOnly: !(parserOptions.afterParse || parserOptions.processStyles) + }; + var thisDoc, j; + for (var i = 0; i < urls.length; i++) { + var baseUrl = cleanURL(defileURL(location.pathname), urls[i]); + if (docsByUrl[baseUrl]) { + // Reloading an existing document + thisDoc = docsByUrl[baseUrl]; + thisDoc.reload = true; + } + else { + thisDoc = new Object(); + thisDoc.baseUrl = baseUrl; + internals.docSet.push(thisDoc); + } + thisDoc.url = urls[i]; + thisDoc.internals = internals; + fetchDoc(thisDoc.url, thisDoc); + } + }; + + function fetchDoc(url, doc, resFunc) { + resFunc = resFunc || function (responseXML) { render(responseXML, doc); }; + + if (typeof ZipFile === 'function' && typeof JSIO === 'object' && typeof JSIO.guessFileType === 'function') { // KMZ support requires these modules loaded + // if url is a data URI scheme, do not guess type based on extension. + if (/^data:[^,]*(kmz)/.test(doc.baseUrl)) { + contentType = JSIO.FileType.Binary; + } else if (/^data:[^,]*(kml|xml)/.test(doc.baseUrl)) { + contentType = JSIO.FileType.XML; + } else if (/^data:/.test(doc.baseUrl)) { + contentType = JSIO.FileType.Unknown; + } else if (parserOptions.forceZip) { + contentType = JSIO.FileType.Binary; + } else { + contentType = JSIO.guessFileType(doc.baseUrl); + } + if (contentType == JSIO.FileType.Binary || contentType == JSIO.FileType.Unknown) { + doc.isCompressed = true; + doc.baseDir = doc.baseUrl + '/'; + geoXML3.fetchZIP(url, resFunc, doc.internals.parser); + return; + } + } + doc.isCompressed = false; + doc.baseDir = defileURL(doc.baseUrl); + geoXML3.fetchXML(url, resFunc); + } + + var hideDocument = function (doc) { + if (!doc) doc = docs[0]; + // Hide the map objects associated with a document + var i; + if (!!doc.markers) { + for (i = 0; i < doc.markers.length; i++) { + if(!!doc.markers[i].infoWindow) doc.markers[i].infoWindow.close(); + doc.markers[i].setVisible(false); + } + } + if (!!doc.ggroundoverlays) { + for (i = 0; i < doc.ggroundoverlays.length; i++) { + doc.ggroundoverlays[i].setOpacity(0); + } + } + if (!!doc.gpolylines) { + for (i=0;i<doc.gpolylines.length;i++) { + if(!!doc.gpolylines[i].infoWindow) doc.gpolylines[i].infoWindow.close(); + doc.gpolylines[i].setMap(null); + } + } + if (!!doc.gpolygons) { + for (i=0;i<doc.gpolygons.length;i++) { + if(!!doc.gpolygons[i].infoWindow) doc.gpolygons[i].infoWindow.close(); + doc.gpolygons[i].setMap(null); + } + } + }; + + var showDocument = function (doc) { + if (!doc) doc = docs[0]; + // Show the map objects associated with a document + var i; + if (!!doc.markers) { + for (i = 0; i < doc.markers.length; i++) { + doc.markers[i].setVisible(true); + } + } + if (!!doc.ggroundoverlays) { + for (i = 0; i < doc.ggroundoverlays.length; i++) { + doc.ggroundoverlays[i].setOpacity(doc.ggroundoverlays[i].percentOpacity_); + } + } + if (!!doc.gpolylines) { + for (i=0;i<doc.gpolylines.length;i++) { + doc.gpolylines[i].setMap(parserOptions.map); + } + } + if (!!doc.gpolygons) { + for (i=0;i<doc.gpolygons.length;i++) { + doc.gpolygons[i].setMap(parserOptions.map); + } + } + }; + + var defaultStyle = { + balloon: { + bgColor: 'ffffffff', + textColor: 'ff000000', + text: "<h3>$[name]</h3>\n<div>$[description]</div>\n<div>$[geDirections]</div>", + displayMode: 'default' + }, + icon: { + scale: 1.0, + dim: { + x: 0, + y: 0, + w: -1, + h: -1 + }, + hotSpot: { + x: 0.5, + y: 0.5, + xunits: 'fraction', + yunits: 'fraction' + } + }, + line: { + color: 'ffffffff', // white (KML default) + colorMode: 'normal', + width: 1.0 + }, + poly: { + color: 'ffffffff', // white (KML default) + colorMode: 'normal', + fill: true, + outline: true + } + }; + + var kmlNS = 'http://www.opengis.net/kml/2.2'; + var gxNS = 'http://www.google.com/kml/ext/2.2'; + var nodeValue = geoXML3.nodeValue; + var getBooleanValue = geoXML3.getBooleanValue; + var getElementsByTagNameNS = geoXML3.getElementsByTagNameNS; + var getElementsByTagName = geoXML3.getElementsByTagName; + + function processStyleUrl(node) { + var styleUrlStr = nodeValue(getElementsByTagName(node, 'styleUrl')[0]); + if (!!styleUrlStr && styleUrlStr.indexOf('#') != -1) + var styleUrl = styleUrlStr.split('#'); + else var styleUrl = ["",""]; + return styleUrl; + } + + function processStyle(thisNode, baseUrl, styleID, baseDir) { + var style = (baseUrl === '{inline}') ? clone(defaultStyle) : (styles[baseUrl][styleID] = styles[baseUrl][styleID] || clone(defaultStyle)); + + var styleNodes = getElementsByTagName(thisNode, 'BalloonStyle'); + if (!!styleNodes && styleNodes.length > 0) { + style.balloon.bgColor = nodeValue(getElementsByTagName(styleNodes[0], 'bgColor')[0], style.balloon.bgColor); + style.balloon.textColor = nodeValue(getElementsByTagName(styleNodes[0], 'textColor')[0], style.balloon.textColor); + style.balloon.text = nodeValue(getElementsByTagName(styleNodes[0], 'text')[0], style.balloon.text); + style.balloon.displayMode = nodeValue(getElementsByTagName(styleNodes[0], 'displayMode')[0], style.balloon.displayMode); + } + + // style.list = (unsupported; doesn't make sense in Google Maps) + + var styleNodes = getElementsByTagName(thisNode, 'IconStyle'); + if (!!styleNodes && styleNodes.length > 0) { + var icon = style.icon; + + icon.scale = parseFloat(nodeValue(getElementsByTagName(styleNodes[0], 'scale')[0], icon.scale)); + // style.icon.heading = (unsupported; not supported in API) + // style.icon.color = (unsupported; not supported in API) + // style.icon.colorMode = (unsupported; not supported in API) + + styleNodes = getElementsByTagName(styleNodes[0], 'hotSpot'); + if (!!styleNodes && styleNodes.length > 0) { + icon.hotSpot = { + x: styleNodes[0].getAttribute('x'), + y: styleNodes[0].getAttribute('y'), + xunits: styleNodes[0].getAttribute('xunits'), + yunits: styleNodes[0].getAttribute('yunits') + }; + } + + styleNodes = getElementsByTagName(thisNode, 'Icon'); + if (!!styleNodes && styleNodes.length > 0) { + icon.href = nodeValue(getElementsByTagName(styleNodes[0], 'href')[0]); + icon.url = cleanURL(baseDir, icon.href); + // Detect images buried in KMZ files (and use a base64 encoded URL) + if (kmzMetaData[icon.url]) icon.url = kmzMetaData[icon.url].dataUrl; + + // Support for icon palettes and exact size dimensions + icon.dim = { + x: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'x')[0], icon.dim.x)), + y: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'y')[0], icon.dim.y)), + w: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'w')[0], icon.dim.w)), + h: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'h')[0], icon.dim.h)) + }; + + // certain occasions where we need the pixel size of the image (like the default settings...) + // (NOTE: Scale is applied to entire image, not just the section of the icon palette. So, + // if we need scaling, we'll need the img dimensions no matter what.) + if (true /* (icon.dim.w < 0 || icon.dim.h < 0) && (icon.xunits != 'pixels' || icon.yunits == 'fraction') || icon.scale != 1.0 */) { + // (hopefully, this will load by the time we need it...) + icon.img = new Image(); + icon.img.onload = function() { + if (icon.dim.w < 0 || icon.dim.h < 0) { + icon.dim.w = this.width; + icon.dim.h = this.height; + } else { + icon.dim.th = this.height; + } + }; + icon.img.src = icon.url; + + // sometimes the file is already cached and it never calls onLoad + if (icon.img.width > 0) { + if (icon.dim.w < 0 || icon.dim.h < 0) { + icon.dim.w = icon.img.width; + icon.dim.h = icon.img.height; + } else { + icon.dim.th = icon.img.height; + } + } + } + } + } + + // style.label = (unsupported; may be possible but not with API) + + styleNodes = getElementsByTagName(thisNode, 'LineStyle'); + if (!!styleNodes && styleNodes.length > 0) { + style.line.color = nodeValue(getElementsByTagName(styleNodes[0], 'color')[0], style.line.color); + style.line.colorMode = nodeValue(getElementsByTagName(styleNodes[0], 'colorMode')[0], style.line.colorMode); + style.line.width = nodeValue(getElementsByTagName(styleNodes[0], 'width')[0], style.line.width); + // style.line.outerColor = (unsupported; not supported in API) + // style.line.outerWidth = (unsupported; not supported in API) + // style.line.physicalWidth = (unsupported; unneccesary in Google Maps) + // style.line.labelVisibility = (unsupported; possible to implement) + } + + styleNodes = getElementsByTagName(thisNode, 'PolyStyle'); + if (!!styleNodes && styleNodes.length > 0) { + style.poly.color = nodeValue( getElementsByTagName(styleNodes[0], 'color')[0], style.poly.color); + style.poly.colorMode = nodeValue( getElementsByTagName(styleNodes[0], 'colorMode')[0], style.poly.colorMode); + style.poly.outline = getBooleanValue(getElementsByTagName(styleNodes[0], 'outline')[0], style.poly.outline); + style.poly.fill = getBooleanValue(getElementsByTagName(styleNodes[0], 'fill')[0], style.poly.fill); + } + return style; + } + + // from http://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-clone-a-javascript-object + // http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone + function clone(obj){ + if(obj == null || typeof(obj) != 'object') return obj; + if (obj.cloneNode) return obj.cloneNode(true); + var temp = new obj.constructor(); + for(var key in obj) temp[key] = clone(obj[key]); + return temp; + } + + function processStyleMap(thisNode, baseUrl, styleID, baseDir) { + var pairs = getElementsByTagName(thisNode, 'Pair'); + var map = new Object(); + + // add each key to the map + for (var pr=0;pr<pairs.length;pr++) { + var pairKey = nodeValue(getElementsByTagName(pairs[pr], 'key')[0]); + var pairStyle = nodeValue(getElementsByTagName(pairs[pr], 'Style')[0]); + var pairStyleUrl = processStyleUrl(pairs[pr]); + var pairStyleBaseUrl = pairStyleUrl[0] ? cleanURL(baseDir, pairStyleUrl[0]) : baseUrl; + var pairStyleID = pairStyleUrl[1]; + + if (!!pairStyle) { + map[pairKey] = processStyle(pairStyle, pairStyleBaseUrl, pairStyleID); + } else if (!!pairStyleID && !!styles[pairStyleBaseUrl][pairStyleID]) { + map[pairKey] = clone(styles[pairStyleBaseUrl][pairStyleID]); + } + } + if (!!map["normal"]) { + styles[baseUrl][styleID] = clone(map["normal"]); + } else { + styles[baseUrl][styleID] = clone(defaultStyle); + } + if (!!map["highlight"] && !!parserOptions.processStyles) { + processStyleID(map["highlight"]); + } + styles[baseUrl][styleID].map = clone(map); + } + + function processPlacemarkCoords(node, tag) { + var parent = getElementsByTagName(node, tag); + var coordListA = []; + for (var i=0; i<parent.length; i++) { + var coordNodes = getElementsByTagName(parent[i], 'coordinates'); + if (!coordNodes) { + if (coordListA.length > 0) { + break; + } else { + return [{coordinates: []}]; + } + } + + for (var j=0; j<coordNodes.length;j++) { + var coords = nodeValue(coordNodes[j]).trim(); + coords = coords.replace(/,\s+/g, ','); + var path = coords.split(/\s+/g); + var pathLength = path.length; + var coordList = []; + for (var k = 0; k < pathLength; k++) { + coords = path[k].split(','); + if (!isNaN(coords[0]) && !isNaN(coords[1])) { + coordList.push({ + lat: parseFloat(coords[1]), + lng: parseFloat(coords[0]), + alt: parseFloat(coords[2]) + }); + } + } + coordListA.push({coordinates: coordList}); + } + } + return coordListA; + } + + var render = function (responseXML, doc) { + // Callback for retrieving a KML document: parse the KML and display it on the map + if (!responseXML || responseXML == "failed parse") { + // Error retrieving the data + geoXML3.log('Unable to retrieve ' + doc.url); + if (parserOptions.failedParse) parserOptions.failedParse(doc); + doc.failed = true; + return; + } else if (responseXML.parseError && responseXML.parseError.errorCode != 0) { + // IE parse error + var err = responseXML.parseError; + var msg = 'Parse error in line ' + err.line + ', col ' + err.linePos + ' (error code: ' + err.errorCode + ")\n" + + "\nError Reason: " + err.reason + + 'Error Line: ' + err.srcText; + + geoXML3.log('Unable to retrieve ' + doc.url + ': ' + msg); + if (parserOptions.failedParse) parserOptions.failedParse(doc); + doc.failed = true; + return; + } else if (responseXML.documentElement && responseXML.documentElement.nodeName == 'parsererror') { + // Firefox parse error + geoXML3.log('Unable to retrieve ' + doc.url + ': ' + responseXML.documentElement.childNodes[0].nodeValue); + if (parserOptions.failedParse) parserOptions.failedParse(doc); + doc.failed = true; + return; + } else if (!doc) { + throw 'geoXML3 internal error: render called with null document'; + } else { //no errors + var i; + doc.placemarks = []; + doc.groundoverlays = []; + doc.ggroundoverlays = []; + doc.networkLinks = []; + doc.gpolygons = []; + doc.gpolylines = []; + + // Check for dependent KML files + var nodes = getElementsByTagName(responseXML, 'styleUrl'); + var docSet = doc.internals.docSet; + + for (var i = 0; i < nodes.length; i++) { + var url = nodeValue(nodes[i]).split('#')[0]; + if (!url) continue; // #id (inside doc) + var rUrl = cleanURL( doc.baseDir, url ); + if (rUrl === doc.baseUrl) continue; // self + if (docsByUrl[rUrl]) continue; // already loaded + + var thisDoc; + var j = docSet.indexOfObjWithItem('baseUrl', rUrl); + if (j != -1) { + // Already listed to be loaded, but probably in the wrong order. + // Load it right away to immediately resolve dependency. + thisDoc = docSet[j]; + if (thisDoc.failed) continue; // failed to load last time; don't retry it again + } + else { + // Not listed at all; add it in + thisDoc = new Object(); + thisDoc.url = rUrl; // url can't be trusted inside KMZ files, since it may .. outside of the archive + thisDoc.baseUrl = rUrl; + thisDoc.internals = doc.internals; + + doc.internals.docSet.push(thisDoc); + doc.internals.remaining++; + } + + // render dependent KML first then re-run renderer + fetchDoc(rUrl, thisDoc, function (thisResXML) { + render(thisResXML, thisDoc); + render(responseXML, doc); + }); + + // to prevent cross-dependency issues, just load the one + // file first and re-check the rest later + return; + } + + // Parse styles + doc.styles = styles[doc.baseUrl] = styles[doc.baseUrl] || {}; + var styleID, styleNodes; + nodes = getElementsByTagName(responseXML, 'Style'); + nodeCount = nodes.length; + for (i = 0; i < nodeCount; i++) { + thisNode = nodes[i]; + var styleID = thisNode.getAttribute('id'); + if (!!styleID) processStyle(thisNode, doc.baseUrl, styleID, doc.baseDir); + } + // Parse StyleMap nodes + nodes = getElementsByTagName(responseXML, 'StyleMap'); + for (i = 0; i < nodes.length; i++) { + thisNode = nodes[i]; + var styleID = thisNode.getAttribute('id'); + if (!!styleID) processStyleMap(thisNode, doc.baseUrl, styleID, doc.baseDir); + } + + if (!!parserOptions.processStyles || !parserOptions.createMarker) { + // Convert parsed styles into GMaps equivalents + processStyles(doc); + } + + // Parse placemarks + if (!!doc.reload && !!doc.markers) { + for (i = 0; i < doc.markers.length; i++) { + doc.markers[i].active = false; + } + } + var placemark, node, coords, path, marker, poly; + var pathLength, marker, polygonNodes, coordList; + var placemarkNodes = getElementsByTagName(responseXML, 'Placemark'); + for (pm = 0; pm < placemarkNodes.length; pm++) { + // Init the placemark object + node = placemarkNodes[pm]; + var styleUrl = processStyleUrl(node); + placemark = { + name: nodeValue(getElementsByTagName(node, 'name')[0]), + description: nodeValue(getElementsByTagName(node, 'description')[0]), + styleUrl: styleUrl.join('#'), + styleBaseUrl: styleUrl[0] ? cleanURL(doc.baseDir, styleUrl[0]) : doc.baseUrl, + styleID: styleUrl[1], + visibility: getBooleanValue(getElementsByTagName(node, 'visibility')[0], true), + balloonVisibility: getBooleanValue(getElementsByTagNameNS(node, gxNS, 'balloonVisibility')[0], !parserOptions.suppressInfoWindows), + id: node.getAttribute('id') + }; + placemark.style = (styles[placemark.styleBaseUrl] && styles[placemark.styleBaseUrl][placemark.styleID]) || clone(defaultStyle); + // inline style overrides shared style + var inlineStyles = getElementsByTagName(node, 'Style'); + if (inlineStyles && (inlineStyles.length > 0)) { + var style = processStyle(node, '{inline}', '{inline}'); + processStyleID(style); + if (style) placemark.style = style; + } + + if (/^https?:\/\//.test(placemark.description)) { + placemark.description = ['<a href="', placemark.description, '">', placemark.description, '</a>'].join(''); + } + + // record list of variables for substitution + placemark.vars = { + display: { + name: 'Name', + description: 'Description', + address: 'Street Address', + id: 'ID', + Snippet: 'Snippet', + geDirections: 'Directions' + }, + val: { + name: placemark.name || '', + description: placemark.description || '', + address: nodeValue(getElementsByTagName(node, 'address')[0], ''), + id: node.getAttribute('id') || '', + Snippet: nodeValue(getElementsByTagName(node, 'Snippet')[0], '') + }, + directions: [ + 'f=d', + 'source=GeoXML3' + ] + }; + + // add extended data to variables + var extDataNodes = getElementsByTagName(node, 'ExtendedData'); + if (!!extDataNodes && extDataNodes.length > 0) { + var dataNodes = getElementsByTagName(extDataNodes[0], 'Data'); + for (var d = 0; d < dataNodes.length; d++) { + var dn = dataNodes[d]; + var name = dn.getAttribute('name'); + if (!name) continue; + var dName = nodeValue(getElementsByTagName(dn, 'displayName')[0], name); + var val = nodeValue(getElementsByTagName(dn, 'value')[0]); + + placemark.vars.val[name] = val; + placemark.vars.display[name] = dName; + } + } + + // process MultiGeometry + var GeometryNodes = getElementsByTagName(node, 'coordinates'); + var Geometry = null; + if (!!GeometryNodes && (GeometryNodes.length > 0)) { + for (var gn=0;gn<GeometryNodes.length;gn++) { + if (GeometryNodes[gn].parentNode && + GeometryNodes[gn].parentNode.nodeName) { + var GeometryPN = GeometryNodes[gn].parentNode; + Geometry = GeometryPN.nodeName; + + // Extract the coordinates + // What sort of placemark? + switch(Geometry) { + case "Point": + placemark.Point = processPlacemarkCoords(node, "Point")[0]; + placemark.latlng = new google.maps.LatLng(placemark.Point.coordinates[0].lat, placemark.Point.coordinates[0].lng); + pathLength = 1; + break; + case "LinearRing": + // Polygon/line + polygonNodes = getElementsByTagName(node, 'Polygon'); + // Polygon + if (!placemark.Polygon) + placemark.Polygon = [{ + outerBoundaryIs: {coordinates: []}, + innerBoundaryIs: [{coordinates: []}] + }]; + for (var pg=0;pg<polygonNodes.length;pg++) { + placemark.Polygon[pg] = { + outerBoundaryIs: {coordinates: []}, + innerBoundaryIs: [{coordinates: []}] + } + placemark.Polygon[pg].outerBoundaryIs = processPlacemarkCoords(polygonNodes[pg], "outerBoundaryIs"); + placemark.Polygon[pg].innerBoundaryIs = processPlacemarkCoords(polygonNodes[pg], "innerBoundaryIs"); + } + coordList = placemark.Polygon[0].outerBoundaryIs; + break; + + case "LineString": + pathLength = 0; + placemark.LineString = processPlacemarkCoords(node,"LineString"); + break; + + default: + break; + } + } + } + } + + // parse MultiTrack/Track + var TrackNodes = getElementsByTagNameNS(node,gxNS,"Track"); + var coordListA = []; + if (TrackNodes.length > 0) { + for (var i=0; i<TrackNodes.length; i++) { + var coordNodes = getElementsByTagNameNS(TrackNodes[i],gxNS,"coord"); + var coordList = []; + for (var j=0; j<coordNodes.length;j++) { + var coords = geoXML3.nodeValue(coordNodes[j]).trim(); + coords = coords.split(/\s+/g); + if (!isNaN(coords[0]) && !isNaN(coords[1])) { + coordList.push({ + lat: parseFloat(coords[1]), + lng: parseFloat(coords[0]), + alt: parseFloat(coords[2]) + }); + } + } + coordListA.push({coordinates:coordList}); + } + placemark.Track = coordListA; + } + + // call the custom placemark parse function if it is defined + if (!!parserOptions.pmParseFn) parserOptions.pmParseFn(node, placemark); + doc.placemarks.push(placemark); + + // single marker + if (placemark.Point) { + if (!!google.maps) { + doc.bounds = doc.bounds || new google.maps.LatLngBounds(); + doc.bounds.extend(placemark.latlng); + } + + // Potential user-defined marker handler + var pointCreateFunc = parserOptions.createMarker || createMarker; + var found = false; + if (!parserOptions.createMarker) { + // Check to see if this marker was created on a previous load of this document + if (!!doc) { + doc.markers = doc.markers || []; + if (doc.reload) { + for (var j = 0; j < doc.markers.length; j++) { + if ((doc.markers[j].id == placemark.id) || + // if no id, check position + (!doc.markers[j].id && + (doc.markers[j].getPosition().equals(placemark.latlng)))) { + found = doc.markers[j].active = true; + break; + } + } + } + } + } + if (!found) { + // Call the marker creator + var marker = pointCreateFunc(placemark, doc); + if (marker) { + marker.active = placemark.visibility; + marker.id = placemark.id; + } + } + } + // polygon/line + var poly, line; + if (!!doc) { + if (placemark.Polygon) doc.gpolygons = doc.gpolygons || []; + if (placemark.LineString) doc.gpolylines = doc.gpolylines || []; + if (placemark.Track) doc.gpolylines = doc.gpolylines || []; + } + + var polyCreateFunc = parserOptions.createPolygon || createPolygon; + var lineCreateFunc = parserOptions.createLineString || createPolyline; + if (placemark.Polygon) { + poly = polyCreateFunc(placemark,doc); + if (poly) poly.active = placemark.visibility; + } + if (placemark.LineString) { + line = lineCreateFunc(placemark,doc); + if (line) line.active = placemark.visibility; + } + if (placemark.Track) { // gx:Track polyline + line = lineCreateFunc(placemark,doc); + if (line) line.active = placemark.visibility; + } + if (!!google.maps) { + doc.bounds = doc.bounds || new google.maps.LatLngBounds(); + if (poly) doc.bounds.union(poly.bounds); + if (line) doc.bounds.union(line.bounds); + } + + } // placemark loop + + if (!!doc.reload && !!doc.markers) { + for (i = doc.markers.length - 1; i >= 0 ; i--) { + if (!doc.markers[i].active) { + if (!!doc.markers[i].infoWindow) { + doc.markers[i].infoWindow.close(); + } + doc.markers[i].setMap(null); + doc.markers.splice(i, 1); + } + } + } + + var overlayCreateFunc = parserOptions.createOverlay || createOverlay; + // Parse ground overlays + if (!!doc.reload && !!doc.groundoverlays) { + for (i = 0; i < doc.groundoverlays.length; i++) { + doc.groundoverlays[i].active = false; + } + } + + if (!!doc) { + doc.groundoverlays = doc.groundoverlays || []; + } + // doc.groundoverlays =[]; + var groundOverlay, color, transparency, overlay; + var groundNodes = getElementsByTagName(responseXML, 'GroundOverlay'); + for (i = 0; i < groundNodes.length; i++) { + node = groundNodes[i]; + + // Detect images buried in KMZ files (and use a base64 encoded URL) + var gnUrl = cleanURL( doc.baseDir, nodeValue(getElementsByTagName(node, 'href')[0]) ); + if (kmzMetaData[gnUrl]) gnUrl = kmzMetaData[gnUrl].dataUrl; + + // Init the ground overlay object + groundOverlay = { + name: nodeValue(getElementsByTagName(node, 'name')[0]), + description: nodeValue(getElementsByTagName(node, 'description')[0]), + icon: { href: gnUrl }, + latLonBox: { + north: parseFloat(nodeValue(getElementsByTagName(node, 'north')[0])), + east: parseFloat(nodeValue(getElementsByTagName(node, 'east')[0])), + south: parseFloat(nodeValue(getElementsByTagName(node, 'south')[0])), + west: parseFloat(nodeValue(getElementsByTagName(node, 'west')[0])) + }, + rotation: -1 * parseFloat(nodeValue(getElementsByTagName(node, 'rotation')[0])) + }; + if (!!google.maps) { + doc.bounds = doc.bounds || new google.maps.LatLngBounds(); + doc.bounds.union(new google.maps.LatLngBounds( + new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west), + new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east) + )); + } + + // Opacity is encoded in the color node + var colorNode = getElementsByTagName(node, 'color'); + if (colorNode && colorNode.length > 0) { + groundOverlay.opacity = geoXML3.getOpacity(nodeValue(colorNode[0])); + } else { + groundOverlay.opacity = 1.0; // KML default + } + + doc.groundoverlays.push(groundOverlay); + // Check to see if this overlay was created on a previous load of this document + var found = false; + if (!!doc) { + doc.groundoverlays = doc.groundoverlays || []; + if (doc.reload) { + overlayBounds = new google.maps.LatLngBounds( + new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west), + new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east) + ); + var overlays = doc.groundoverlays; + for (i = overlays.length; i--;) { + if ((overlays[i].bounds().equals(overlayBounds)) && + (overlays.url_ === groundOverlay.icon.href)) { + found = overlays[i].active = true; + break; + } + } + } + + if (!found) { + overlay = overlayCreateFunc(groundOverlay, doc); + overlay.active = true; + } + } + if (!!doc.reload && !!doc.groundoverlays && !!doc.groundoverlays.length) { + var overlays = doc.groundoverlays; + for (i = overlays.length; i--;) { + if (!overlays[i].active) { + overlays[i].remove(); + overlays.splice(i, 1); + } + } + doc.groundoverlays = overlays; + } + } + + // Parse network links + var networkLink; + var docPath = document.location.pathname.split('/'); + docPath = docPath.splice(0, docPath.length - 1).join('/'); + var linkNodes = getElementsByTagName(responseXML, 'NetworkLink'); + for (i = 0; i < linkNodes.length; i++) { + node = linkNodes[i]; + + // Init the network link object + networkLink = { + name: nodeValue(getElementsByTagName(node, 'name')[0]), + link: { + href: nodeValue(getElementsByTagName(node, 'href')[0]), + refreshMode: nodeValue(getElementsByTagName(node, 'refreshMode')[0]) + } + }; + + // Establish the specific refresh mode + if (!networkLink.link.refreshMode) { + networkLink.link.refreshMode = 'onChange'; + } + if (networkLink.link.refreshMode === 'onInterval') { + networkLink.link.refreshInterval = parseFloat(nodeValue(getElementsByTagName(node, 'refreshInterval')[0])); + if (isNaN(networkLink.link.refreshInterval)) { + networkLink.link.refreshInterval = 0; + } + } else if (networkLink.link.refreshMode === 'onChange') { + networkLink.link.viewRefreshMode = nodeValue(getElementsByTagName(node, 'viewRefreshMode')[0]); + if (!networkLink.link.viewRefreshMode) { + networkLink.link.viewRefreshMode = 'never'; + } + if (networkLink.link.viewRefreshMode === 'onStop') { + networkLink.link.viewRefreshTime = nodeValue(getElementsByTagName(node, 'refreshMode')[0]); + networkLink.link.viewFormat = nodeValue(getElementsByTagName(node, 'refreshMode')[0]); + if (!networkLink.link.viewFormat) { + networkLink.link.viewFormat = 'BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth]'; + } + } + } + + if (!/^[\/|http]/.test(networkLink.link.href)) { + // Fully-qualify the HREF + networkLink.link.href = docPath + '/' + networkLink.link.href; + } + + // Apply the link + if ((networkLink.link.refreshMode === 'onInterval') && + (networkLink.link.refreshInterval > 0)) { + // Reload at regular intervals + setInterval(parserName + '.parse("' + networkLink.link.href + '")', + 1000 * networkLink.link.refreshInterval); + } else if (networkLink.link.refreshMode === 'onChange') { + if (networkLink.link.viewRefreshMode === 'never') { + // Load the link just once + doc.internals.parser.parse(networkLink.link.href, doc.internals.docSet); + } else if (networkLink.link.viewRefreshMode === 'onStop') { + // Reload when the map view changes + + } + } + } + } + + if (!!doc.bounds) { + doc.internals.bounds = doc.internals.bounds || new google.maps.LatLngBounds(); + doc.internals.bounds.union(doc.bounds); + } + if (!!doc.markers || !!doc.groundoverlays || !!doc.gpolylines || !!doc.gpolygons) { + doc.internals.parseOnly = false; + } + + if (!doc.internals.parseOnly) { + // geoXML3 is not being used only as a real-time parser, so keep the processed documents around + if (doc.baseUrl){ // handle case from parseKmlString (no doc.baseUrl) + if (!docsByUrl[doc.baseUrl]) { + docs.push(doc); + docsByUrl[doc.baseUrl] = doc; + } else { + // internal replacement, which keeps the same memory ref loc in docs and docsByUrl + for (var i in docsByUrl[doc.baseUrl]) { + docsByUrl[doc.baseUrl][i] = doc[i]; + } + } + } + } + + doc.internals.remaining--; + if (doc.internals.remaining === 0) { + // We're done processing this set of KML documents + // Options that get invoked after parsing completes + if (parserOptions.zoom && !!doc.internals.bounds && + !doc.internals.bounds.isEmpty() && !!parserOptions.map) { + parserOptions.map.fitBounds(doc.internals.bounds); + } + if (parserOptions.afterParse) { + parserOptions.afterParse(doc.internals.docSet); + } + google.maps.event.trigger(doc.internals.parser, 'parsed'); + } + }; + + var kmlColor = function (kmlIn, colorMode) { + var kmlColor = {}; + kmlIn = kmlIn || 'ffffffff'; // white (KML 2.2 default) + + var aa = kmlIn.substr(0,2); + var bb = kmlIn.substr(2,2); + var gg = kmlIn.substr(4,2); + var rr = kmlIn.substr(6,2); + + kmlColor.opacity = parseInt(aa, 16) / 256; + kmlColor.color = (colorMode === 'random') ? randomColor(rr, gg, bb) : '#' + rr + gg + bb; + return kmlColor; + }; + + // Implemented per KML 2.2 <ColorStyle> specs + var randomColor = function(rr, gg, bb) { + var col = { rr: rr, gg: gg, bb: bb }; + for (var k in col) { + var v = col[k]; + if (v == null) v = 'ff'; + + // RGB values are limiters for random numbers (ie: 7f would be a random value between 0 and 7f) + v = Math.round(Math.random() * parseInt(rr, 16)).toString(16); + if (v.length === 1) v = '0' + v; + col[k] = v; + } + + return '#' + col.rr + col.gg + col.bb; + }; + + var processStyleID = function (style) { + var icon = style.icon; + if (!icon || !icon.href) return; + + if (icon.img && !icon.img.complete && (icon.dim.w < 0) && (icon.dim.h < 0) ) { + // we're still waiting on the image loading (probably because we've been blocking since the declaration) + // so, let's queue this function on the onload stack + icon.markerBacklog = []; + icon.img.onload = function() { + if (icon.dim.w < 0 || icon.dim.h < 0) { + icon.dim.w = this.width; + icon.dim.h = this.height; + } else { + icon.dim.th = this.height; + } + processStyleID(style); + + // we will undoubtedly get some createMarker queuing, so set this up in advance + for (var i = 0; i < icon.markerBacklog.length; i++) { + var p = icon.markerBacklog[i][0]; + var d = icon.markerBacklog[i][1]; + createMarker(p, d); + if (p.marker) p.marker.active = true; + } + delete icon.markerBacklog; + }; + return; + } + else { //if (icon.dim.w < 0 || icon.dim.h < 0) { + if (icon.img && icon.img.complete) { + // sometimes the file is already cached and it never calls onLoad + if (icon.dim.w < 0 || icon.dim.h < 0) { + icon.dim.w = icon.img.width; + icon.dim.h = icon.img.height; + } else { + icon.dim.th = icon.img.height; + } + } + else { + // settle for a default of 32x32 + icon.dim.whGuess = true; + icon.dim.w = 32; + icon.dim.h = 32; + icon.dim.th = 32; + } + } + + // pre-scaled variables + var rnd = Math.round; + var y = icon.dim.y; + if (typeof icon.dim.th !== 'undefined' && icon.dim.th != icon.dim.h) { // palette - reverse kml y for maps + y = Math.abs(y - (icon.dim.th - icon.dim.h)); + } + + var scaled = { + x: icon.dim.x * icon.scale, + y: y * icon.scale, + w: icon.dim.w * icon.scale, + h: icon.dim.h * icon.scale, + aX:icon.hotSpot.x * icon.scale, + aY:icon.hotSpot.y * icon.scale, + iW:(icon.img ? icon.img.width : icon.dim.w) * icon.scale, + iH:(icon.img ? icon.img.height : icon.dim.h) * icon.scale + }; + + // Figure out the anchor spot + // Origins, anchor positions and coordinates of the marker increase in the X direction to the right and in + // the Y direction down. + var aX, aY; + switch (icon.hotSpot.xunits) { + case 'fraction': aX = rnd(scaled.aX * icon.dim.w); break; + case 'insetPixels': aX = rnd(icon.dim.w * icon.scale - scaled.aX); break; + default: aX = rnd(scaled.aX); break; // already pixels + } + switch(icon.hotSpot.yunits) { + case 'fraction': aY = scaled.h - rnd(icon.dim.h * scaled.aY); break; + case 'insetPixels': aY = rnd(scaled.aY); break; + default: aY = rnd(icon.dim.h * icon.scale - scaled.aY); break; + } + var iconAnchor = new google.maps.Point(aX, aY); + + // Sizes + // (NOTE: Scale is applied to entire image, not just the section of the icon palette.) + var iconSize = icon.dim.whGuess ? null : new google.maps.Size(rnd(scaled.w), rnd(scaled.h)); + var iconScale = icon.scale == 1.0 ? null : + icon.dim.whGuess ? new google.maps.Size(rnd(scaled.w), rnd(scaled.h)) + : new google.maps.Size(rnd(scaled.iW), rnd(scaled.iH)); + var iconOrigin = new google.maps.Point(rnd(scaled.x), rnd(scaled.y)); + + // Detect images buried in KMZ files (and use a base64 encoded URL) + if (kmzMetaData[icon.url]) icon.url = kmzMetaData[icon.url].dataUrl; + + // Init the style object with the KML icon + icon.marker = { + url: icon.url, // url + size: iconSize, // size + origin: iconOrigin, // origin + anchor: iconAnchor, // anchor + scaledSize: iconScale // scaledSize + }; + + // Look for a predictable shadow + var stdRegEx = /\/(red|blue|green|yellow|lightblue|purple|pink|orange)(-dot)?\.png/; + var shadowSize = new google.maps.Size(59, 32); + var shadowPoint = new google.maps.Point(16, 32); + if (stdRegEx.test(icon.href)) { + // A standard GMap-style marker icon + icon.shadow = { + url: 'http://maps.google.com/mapfiles/ms/micons/msmarker.shadow.png', // url + size: shadowSize, // size + origin: null, // origin + anchor: shadowPoint, // anchor + scaledSize: shadowSize // scaledSize + }; + } else if (icon.href.indexOf('-pushpin.png') > -1) { + // Pushpin marker icon + icon.shadow = { + url: 'http://maps.google.com/mapfiles/ms/micons/pushpin_shadow.png', // url + size: shadowSize, // size + origin: null, // origin + anchor: shadowPoint, // anchor + scaledSize: shadowSize // scaledSize + }; + } /* else { // Other MyMaps KML standard icon icon.shadow = new google.maps.MarkerImage( icon.href.replace('.png', '.shadow.png'), // url @@ -1033,251 +1155,293 @@ function processStyleUrl(node) { shadowSize // scaledSize ); } */ - } - - var processStyles = function (doc) { - for (var styleID in doc.styles) { - processStyleID(doc.styles[styleID]); - } - }; - - var createMarker = function (placemark, doc) { - // create a Marker to the map from a placemark KML object - var icon = placemark.style.icon; - - if ( !icon.marker && icon.img ) { - // yay, single point of failure is holding up multiple markers... - icon.markerBacklog = icon.markerBacklog || []; - icon.markerBacklog.push([placemark, doc]); - return; - } - - // Load basic marker properties - var markerOptions = geoXML3.combineOptions(parserOptions.markerOptions, { - map: parserOptions.map, - position: new google.maps.LatLng(placemark.Point.coordinates[0].lat, placemark.Point.coordinates[0].lng), - title: placemark.name, - zIndex: Math.round(placemark.Point.coordinates[0].lat * -100000)<<5, - icon: icon.marker, - shadow: icon.shadow, - flat: !icon.shadow, - visible: placemark.visibility - }); - - // Create the marker on the map - var marker = new google.maps.Marker(markerOptions); - if (!!doc) doc.markers.push(marker); - - // Set up and create the infowindow if it is not suppressed - createInfoWindow(placemark, doc, marker); - placemark.marker = marker; - return marker; - }; - - var createOverlay = function (groundOverlay, doc) { - // Add a ProjectedOverlay to the map from a groundOverlay KML object - - if (!window.ProjectedOverlay) { - throw 'geoXML3 error: ProjectedOverlay not found while rendering GroundOverlay from KML'; - } - - var bounds = new google.maps.LatLngBounds( - new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west), - new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east) - ); - var overlayOptions = geoXML3.combineOptions(parserOptions.overlayOptions, {percentOpacity: groundOverlay.opacity*100}); - var overlay = new ProjectedOverlay(parserOptions.map, groundOverlay.icon.href, bounds, overlayOptions); - - if (!!doc) { - doc.ggroundoverlays = doc.ggroundoverlays || []; - doc.ggroundoverlays.push(overlay); - } - - return overlay; - }; - - // Create Polyline - var createPolyline = function(placemark, doc) { - var path = []; - for (var j=0; j<placemark.LineString.length; j++) { - var coords = placemark.LineString[j].coordinates; - var bounds = new google.maps.LatLngBounds(); - for (var i=0;i<coords.length;i++) { - var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng); - path.push(pt); - bounds.extend(pt); - } - } - // point to open the infowindow if triggered - var point = path[Math.floor(path.length/2)]; - // Load basic polyline properties - var kmlStrokeColor = kmlColor(placemark.style.line.color, placemark.style.line.colorMode); - var polyOptions = geoXML3.combineOptions(parserOptions.polylineOptions, { - map: parserOptions.map, - path: path, - strokeColor: kmlStrokeColor.color, - strokeWeight: placemark.style.line.width, - strokeOpacity: kmlStrokeColor.opacity, - title: placemark.name, - visible: placemark.visibility - }); - var p = new google.maps.Polyline(polyOptions); - p.bounds = bounds; - - // setup and create the infoWindow if it is not suppressed - createInfoWindow(placemark, doc, p); - if (!!doc) doc.gpolylines.push(p); - placemark.polyline = p; - return p; - } - - // Create Polygon - var createPolygon = function(placemark, doc) { - var bounds = new google.maps.LatLngBounds(); - var pathsLength = 0; - var paths = []; - for (var polygonPart=0;polygonPart<placemark.Polygon.length;polygonPart++) { - for (var j=0; j<placemark.Polygon[polygonPart].outerBoundaryIs.length; j++) { - var coords = placemark.Polygon[polygonPart].outerBoundaryIs[j].coordinates; - var path = []; - for (var i=0;i<coords.length;i++) { - var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng); - path.push(pt); - bounds.extend(pt); - } - paths.push(path); - pathsLength += path.length; - } - for (var j=0; j<placemark.Polygon[polygonPart].innerBoundaryIs.length; j++) { - var coords = placemark.Polygon[polygonPart].innerBoundaryIs[j].coordinates; - var path = []; - for (var i=0;i<coords.length;i++) { - var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng); - path.push(pt); - bounds.extend(pt); - } - paths.push(path); - pathsLength += path.length; - } - } - - // Load basic polygon properties - var kmlStrokeColor = kmlColor(placemark.style.line.color, placemark.style.line.colorMode); - var kmlFillColor = kmlColor(placemark.style.poly.color, placemark.style.poly.colorMode); - if (!placemark.style.poly.fill) kmlFillColor.opacity = 0.0; - var strokeWeight = placemark.style.line.width; - if (!placemark.style.poly.outline) { - strokeWeight = 0; - kmlStrokeColor.opacity = 0.0; - } - var polyOptions = geoXML3.combineOptions(parserOptions.polygonOptions, { - map: parserOptions.map, - paths: paths, - title: placemark.name, - strokeColor: kmlStrokeColor.color, - strokeWeight: strokeWeight, - strokeOpacity: kmlStrokeColor.opacity, - fillColor: kmlFillColor.color, - fillOpacity: kmlFillColor.opacity, - visible: placemark.visibility - }); - var p = new google.maps.Polygon(polyOptions); - p.bounds = bounds; - - createInfoWindow(placemark, doc, p); - if (!!doc) doc.gpolygons.push(p); - placemark.polygon = p; - return p; - } - - var createInfoWindow = function(placemark, doc, gObj) { - var bStyle = placemark.style.balloon; - var vars = placemark.vars; - - if (!placemark.balloonVisibility || bStyle.displayMode === 'hide') return; - - // define geDirections - if (placemark.latlng) { - vars.directions.push('sll=' + placemark.latlng.toUrlValue()); - - var url = 'http://maps.google.com/maps?' + vars.directions.join('&'); - var address = encodeURIComponent( vars.val.address || placemark.latlng.toUrlValue() ).replace(/\%20/g, '+'); - - vars.val.geDirections = '<a href="' + url + '&daddr=' + address + '" target=_blank>To Here</a> - <a href="' + url + '&saddr=' + address + '" target=_blank>From Here</a>'; - } - else vars.val.geDirections = ''; - - // add in the variables - var iwText = bStyle.text.replace(/\$\[(\w+(\/displayName)?)\]/g, function(txt, n, dn) { return dn ? vars.display[n] : vars.val[n]; }); - var classTxt = 'geoxml3_infowindow geoxml3_style_' + placemark.styleID; - - // color styles - var styleArr = []; - if (bStyle.bgColor != 'ffffffff') styleArr.push('background: ' + kmlColor(bStyle.bgColor ).color + ';'); - if (bStyle.textColor != 'ff000000') styleArr.push('color: ' + kmlColor(bStyle.textColor).color + ';'); - var styleProp = styleArr.length ? ' style="' + styleArr.join(' ') + '"' : ''; - - var infoWindowOptions = geoXML3.combineOptions(parserOptions.infoWindowOptions, { - content: '<div class="' + classTxt + '"' + styleProp + '>' + iwText + '</div>', - pixelOffset: new google.maps.Size(0, 2) - }); - - gObj.infoWindow = parserOptions.infoWindow || new google.maps.InfoWindow(infoWindowOptions); - gObj.infoWindowOptions = infoWindowOptions; - - // Info Window-opening event handler - google.maps.event.addListener(gObj, 'click', function(e) { - var iW = this.infoWindow; - iW.close(); - iW.setOptions(this.infoWindowOptions); - - if (e && e.latLng) iW.setPosition(e.latLng); - else if (this.bounds) iW.setPosition(this.bounds.getCenter()); - - iW.open(this.map, this.bounds ? null : this); - }); - - } - - return { - // Expose some properties and methods - - options: parserOptions, - docs: docs, - docsByUrl: docsByUrl, - kmzMetaData: kmzMetaData, - - parse: parse, - parseKmlString: parseKmlString, - hideDocument: hideDocument, - showDocument: showDocument, - processStyles: processStyles, - createMarker: createMarker, - createOverlay: createOverlay, - createPolyline: createPolyline, - createPolygon: createPolygon - }; + } + + var processStyles = function (doc) { + for (var styleID in doc.styles) { + processStyleID(doc.styles[styleID]); + } + }; + + var createMarker = function (placemark, doc) { + // create a Marker to the map from a placemark KML object + var icon = placemark.style.icon; + + if ( !icon.marker && icon.img ) { + // yay, single point of failure is holding up multiple markers... + icon.markerBacklog = icon.markerBacklog || []; + icon.markerBacklog.push([placemark, doc]); + return; + } + + // Load basic marker properties + var markerOptions = geoXML3.combineOptions(parserOptions.markerOptions, { + map: parserOptions.map, + position: new google.maps.LatLng(placemark.Point.coordinates[0].lat, placemark.Point.coordinates[0].lng), + title: placemark.name, + zIndex: Math.round(placemark.Point.coordinates[0].lat * -100000)<<5, + icon: icon.marker, + shadow: icon.shadow, + flat: !icon.shadow, + visible: placemark.visibility + }); + + // Create the marker on the map + var marker = new google.maps.Marker(markerOptions); + if (!!doc) doc.markers.push(marker); + + // Set up and create the infowindow if it is not suppressed + createInfoWindow(placemark, doc, marker); + placemark.marker = marker; + return marker; + }; + + var createOverlay = function (groundOverlay, doc) { + // Add a ProjectedOverlay to the map from a groundOverlay KML object + + if (!window.ProjectedOverlay) { + throw 'geoXML3 error: ProjectedOverlay not found while rendering GroundOverlay from KML'; + } + + var bounds = new google.maps.LatLngBounds( + new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west), + new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east) + ); + var overlayOptions = geoXML3.combineOptions(parserOptions.overlayOptions, { + percentOpacity: groundOverlay.opacity*100, + rotation: groundOverlay.rotation + }); + var overlay = new ProjectedOverlay(parserOptions.map, groundOverlay.icon.href, bounds, overlayOptions); + + if (!!doc) { + doc.ggroundoverlays = doc.ggroundoverlays || []; + doc.ggroundoverlays.push(overlay); + } + + return overlay; + }; + + // Create Polyline + var createPolyline = function(placemark, doc) { + var paths = []; + var bounds = new google.maps.LatLngBounds(); + if (placemark.LineString) { + for (var j=0; j<placemark.LineString.length; j++) { + var path = []; + var coords = placemark.LineString[j].coordinates; + for (var i=0;i<coords.length;i++) { + var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng); + path.push(pt); + bounds.extend(pt); + } + paths.push(path); + } + } else if (placemark.Track) { + for (var j=0; j<placemark.Track.length; j++) { + var path = []; + var coords = placemark.Track[j].coordinates; + for (var i=0;i<coords.length;i++) { + var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng); + path.push(pt); + bounds.extend(pt); + } + paths.push(path); + } + } + // point to open the infowindow if triggered + var point = paths[0][Math.floor(path.length/2)]; + // Load basic polyline properties + var kmlStrokeColor = kmlColor(placemark.style.line.color, placemark.style.line.colorMode); + var polyOptions = geoXML3.combineOptions(parserOptions.polylineOptions, { + map: parserOptions.map, + path: path, + strokeColor: kmlStrokeColor.color, + strokeWeight: placemark.style.line.width, + strokeOpacity: kmlStrokeColor.opacity, + title: placemark.name, + visible: placemark.visibility + }); + if (paths.length > 1) { + polyOptions.paths = paths; + var p = new MultiGeometry(polyOptions); + } else { + polyOptions.path = paths[0]; + var p = new google.maps.Polyline(polyOptions); + } + p.bounds = bounds; + + // setup and create the infoWindow if it is not suppressed + createInfoWindow(placemark, doc, p); + if (!!doc) doc.gpolylines.push(p); + placemark.polyline = p; + return p; + } + + // Create Polygon + var createPolygon = function(placemark, doc) { + var bounds = new google.maps.LatLngBounds(); + var pathsLength = 0; + var paths = []; + for (var polygonPart=0;polygonPart<placemark.Polygon.length;polygonPart++) { + for (var j=0; j<placemark.Polygon[polygonPart].outerBoundaryIs.length; j++) { + var coords = placemark.Polygon[polygonPart].outerBoundaryIs[j].coordinates; + var path = []; + for (var i=0;i<coords.length;i++) { + var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng); + path.push(pt); + bounds.extend(pt); + } + paths.push(path); + pathsLength += path.length; + } + for (var j=0; j<placemark.Polygon[polygonPart].innerBoundaryIs.length; j++) { + var coords = placemark.Polygon[polygonPart].innerBoundaryIs[j].coordinates; + var path = []; + for (var i=0;i<coords.length;i++) { + var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng); + path.push(pt); + bounds.extend(pt); + } + paths.push(path); + pathsLength += path.length; + } + } + + // Load basic polygon properties + var kmlStrokeColor = kmlColor(placemark.style.line.color, placemark.style.line.colorMode); + var kmlFillColor = kmlColor(placemark.style.poly.color, placemark.style.poly.colorMode); + if (!placemark.style.poly.fill) kmlFillColor.opacity = 0.0; + var strokeWeight = placemark.style.line.width; + if (!placemark.style.poly.outline) { + strokeWeight = 0; + kmlStrokeColor.opacity = 0.0; + } + var polyOptions = geoXML3.combineOptions(parserOptions.polygonOptions, { + map: parserOptions.map, + paths: paths, + title: placemark.name, + strokeColor: kmlStrokeColor.color, + strokeWeight: strokeWeight, + strokeOpacity: kmlStrokeColor.opacity, + fillColor: kmlFillColor.color, + fillOpacity: kmlFillColor.opacity, + visible: placemark.visibility + }); + var p = new google.maps.Polygon(polyOptions); + p.bounds = bounds; + + createInfoWindow(placemark, doc, p); + if (!!doc) doc.gpolygons.push(p); + placemark.polygon = p; + return p; + } + + var createInfoWindow = function(placemark, doc, gObj) { + var bStyle = placemark.style.balloon; + var vars = placemark.vars; + + if (!placemark.balloonVisibility || bStyle.displayMode === 'hide') return; + + // define geDirections + if (placemark.latlng && + (!parserOptions.suppressDirections || !parserOptions.suppressDirections)) { + vars.directions.push('sll=' + placemark.latlng.toUrlValue()); + + var url = 'http://maps.google.com/maps?' + vars.directions.join('&'); + var address = encodeURIComponent( vars.val.address || placemark.latlng.toUrlValue() ).replace(/\%20/g, '+'); + + vars.val.geDirections = '<a href="' + url + '&daddr=' + address + '" target=_blank>To Here</a> - <a href="' + url + '&saddr=' + address + '" target=_blank>From Here</a>'; + } + else vars.val.geDirections = ''; + + // add in the variables + var iwText = bStyle.text.replace(/\$\[(\w+(\/displayName)?)\]/g, function(txt, n, dn) { return dn ? vars.display[n] : vars.val[n]; }); + var classTxt = 'geoxml3_infowindow geoxml3_style_' + placemark.styleID; + + // color styles + var styleArr = []; + if (bStyle.bgColor != 'ffffffff') styleArr.push('background: ' + kmlColor(bStyle.bgColor ).color + ';'); + if (bStyle.textColor != 'ff000000') styleArr.push('color: ' + kmlColor(bStyle.textColor).color + ';'); + var styleProp = styleArr.length ? ' style="' + styleArr.join(' ') + '"' : ''; + + var infoWindowOptions = geoXML3.combineOptions(parserOptions.infoWindowOptions, { + content: '<div class="' + classTxt + '"' + styleProp + '>' + iwText + '</div>', + pixelOffset: new google.maps.Size(0, 2) + }); + + gObj.infoWindow = parserOptions.infoWindow || new google.maps.InfoWindow(infoWindowOptions); + gObj.infoWindowOptions = infoWindowOptions; + + // Info Window-opening event handler + google.maps.event.addListener(gObj, 'click', function(e) { + var iW = this.infoWindow; + iW.close(); + iW.setOptions(this.infoWindowOptions); + + if (e && e.latLng) iW.setPosition(e.latLng); + else if (this.bounds) iW.setPosition(this.bounds.getCenter()); + + iW.setContent("<div id='geoxml3_infowindow'>"+iW.getContent()+"</div>"); + google.maps.event.addListenerOnce(iW, "domready", function() { + var node = document.getElementById('geoxml3_infowindow'); + var imgArray = node.getElementsByTagName('img'); + for (var i = 0; i < imgArray.length; i++) + { + var imgUrlIE = imgArray[i].getAttribute("src"); + var imgUrl = cleanURL(doc.baseDir, imgUrlIE); + + if (kmzMetaData[imgUrl]) { + imgArray[i].src = kmzMetaData[imgUrl].dataUrl; + } else if (kmzMetaData[imgUrlIE]) { + imgArray[i].src = kmzMetaData[imgUrlIE].dataUrl; + } + } + }); + iW.open(this.map, this.bounds ? null : this); + }); + + } + + return { + // Expose some properties and methods + + options: parserOptions, + docs: docs, + docsByUrl: docsByUrl, + kmzMetaData: kmzMetaData, + + parse: parse, + render: render, + parseKmlString: parseKmlString, + hideDocument: hideDocument, + showDocument: showDocument, + processStyles: processStyles, + createMarker: createMarker, + createOverlay: createOverlay, + createPolyline: createPolyline, + createPolygon: createPolygon + }; }; // End of KML Parser // Helper objects and functions geoXML3.getOpacity = function (kmlColor) { - // Extract opacity encoded in a KML color value. Returns a number between 0 and 1. - if (!!kmlColor && - (kmlColor !== '') && - (kmlColor.length == 8)) { - var transparency = parseInt(kmlColor.substr(0, 2), 16); - return transparency / 255; - } else { - return 1; - } + // Extract opacity encoded in a KML color value. Returns a number between 0 and 1. + if (!!kmlColor && + (kmlColor !== '') && + (kmlColor.length == 8)) { + var transparency = parseInt(kmlColor.substr(0, 2), 16); + return transparency / 255; + } else { + return 1; + } }; // Log a message to the debugging console, if one exists geoXML3.log = function(msg) { - if (!!window.console) { - console.log(msg); - } else { alert("log:"+msg); } + if (!!window.console) { + console.log(msg); + } else { alert("log:"+msg); } }; /** @@ -1290,61 +1454,61 @@ geoXML3.log = function(msg) { * @property {ProjectedOverlay.options} overlayOptions If the parser is adding ProjectedOverlays to the map itself, any options specified here will be applied to them. */ geoXML3.parserOptions = function (overrides) { - this.map = null, - /** If true, the parser will automatically move the map to a best-fit of the geodata after parsing of a KML document completes. - * @type Boolean - * @default true - */ - this.zoom = true, - /**#@+ @type Boolean - * @default false */ - /** If true, only a single Marker created by the parser will be able to have its InfoWindow open at once (simulating the behavior of GMaps API v2). */ - this.singleInfoWindow = false, - /** If true, suppresses the rendering of info windows. */ - this.suppressInfoWindows = false, - /** - * Control whether to process styles now or later. - * - * <p>By default, the parser only processes KML <Style> elements into their GMaps equivalents - * if it will be creating its own Markers (the createMarker option is null). Setting this option - * to true will force such processing to happen anyway, useful if you're going to be calling parser.createMarker - * yourself later. OTOH, leaving this option false removes runtime dependency on the GMaps API, enabling - * the use of geoXML3 as a standalone KML parser.</p> - */ - this.processStyles = false, - /**#@-*/ - - this.markerOptions = {}, - this.infoWindowOptions = {}, - this.overlayOptions = {}, - - /**#@+ @event */ - /** This function will be called when parsing of a KML document is complete. - * @param {geoXML3.parser#docs} doc Parsed KML data. */ - this.afterParse = null, - /** This function will be called when parsing of a KML document is complete. - * @param {geoXML3.parser#docs} doc Parsed KML data. */ - this.failedParse = null, - /** - * If supplied, this function will be called once for each marker <Placemark> in the KML document, instead of the parser adding its own Marker to the map. - * @param {geoXML3.parser.render#placemark} placemark Placemark object. - * @param {geoXML3.parser#docs} doc Parsed KML data. - */ - this.createMarker = null, - /** - * If supplied, this function will be called once for each <GroundOverlay> in the KML document, instead of the parser adding its own ProjectedOverlay to the map. - * @param {geoXML3.parser.render#groundOverlay} groundOverlay GroundOverlay object. - * @param {geoXML3.parser#docs} doc Parsed KML data. - */ - this.createOverlay = null - /**#@-*/ - - if (overrides) { - for (var prop in overrides) { - if (overrides.hasOwnProperty(prop)) this[prop] = overrides[prop]; - } - } - return this; + this.map = null, + /** If true, the parser will automatically move the map to a best-fit of the geodata after parsing of a KML document completes. + * @type Boolean + * @default true + */ + this.zoom = true, + /**#@+ @type Boolean + * @default false */ + /** If true, only a single Marker created by the parser will be able to have its InfoWindow open at once (simulating the behavior of GMaps API v2). */ + this.singleInfoWindow = false, + /** If true, suppresses the rendering of info windows. */ + this.suppressInfoWindows = false, + /** + * Control whether to process styles now or later. + * + * <p>By default, the parser only processes KML <Style> elements into their GMaps equivalents + * if it will be creating its own Markers (the createMarker option is null). Setting this option + * to true will force such processing to happen anyway, useful if you're going to be calling parser.createMarker + * yourself later. OTOH, leaving this option false removes runtime dependency on the GMaps API, enabling + * the use of geoXML3 as a standalone KML parser.</p> + */ + this.processStyles = false, + /**#@-*/ + + this.markerOptions = {}, + this.infoWindowOptions = {}, + this.overlayOptions = {}, + + /**#@+ @event */ + /** This function will be called when parsing of a KML document is complete. + * @param {geoXML3.parser#docs} doc Parsed KML data. */ + this.afterParse = null, + /** This function will be called when parsing of a KML document is complete. + * @param {geoXML3.parser#docs} doc Parsed KML data. */ + this.failedParse = null, + /** + * If supplied, this function will be called once for each marker <Placemark> in the KML document, instead of the parser adding its own Marker to the map. + * @param {geoXML3.parser.render#placemark} placemark Placemark object. + * @param {geoXML3.parser#docs} doc Parsed KML data. + */ + this.createMarker = null, + /** + * If supplied, this function will be called once for each <GroundOverlay> in the KML document, instead of the parser adding its own ProjectedOverlay to the map. + * @param {geoXML3.parser.render#groundOverlay} groundOverlay GroundOverlay object. + * @param {geoXML3.parser#docs} doc Parsed KML data. + */ + this.createOverlay = null + /**#@-*/ + + if (overrides) { + for (var prop in overrides) { + if (overrides.hasOwnProperty(prop)) this[prop] = overrides[prop]; + } + } + return this; }; /** @@ -1356,18 +1520,18 @@ geoXML3.parserOptions = function (overrides) { * @return {geoXML3.parserOptions} Combined result. */ geoXML3.combineOptions = function (overrides, defaults) { - var result = {}; - if (!!overrides) { - for (var prop in overrides) { - if (overrides.hasOwnProperty(prop)) result[prop] = overrides[prop]; - } - } - if (!!defaults) { - for (prop in defaults) { - if (defaults.hasOwnProperty(prop) && result[prop] === undefined) result[prop] = defaults[prop]; - } - } - return result; + var result = {}; + if (!!overrides) { + for (var prop in overrides) { + if (overrides.hasOwnProperty(prop)) result[prop] = overrides[prop]; + } + } + if (!!defaults) { + for (prop in defaults) { + if (defaults.hasOwnProperty(prop) && result[prop] === undefined) result[prop] = defaults[prop]; + } + } + return result; }; /** @@ -1394,35 +1558,43 @@ geoXML3.fetchers = []; * @return {Element|Document} DOM. */ geoXML3.xmlParse = function (str) { - if (!!window.DOMParser) return (new DOMParser()).parseFromString(str, 'text/xml'); - else if (!!window.ActiveXObject) { - var doc; - - // the many versions of IE's DOM parsers - var AXOs = [ - 'MSXML2.DOMDocument.6.0', - 'MSXML2.DOMDocument.5.0', - 'MSXML2.DOMDocument.4.0', - 'MSXML2.DOMDocument.3.0', - 'MSXML2.DOMDocument', - 'Microsoft.XMLDOM', - 'MSXML.DOMDocument' - ]; - for (var i = 0; i < AXOs.length; i++) { - try { doc = new ActiveXObject(AXOs[i]); break; } - catch(e) { continue; } - } - if (!doc) return createElement('div', null); - - if (doc.async) doc.async = false; - doc.loadXML(str); - return doc; - } - - return createElement('div', null); + if ((typeof ActiveXObject != 'undefined') || ("ActiveXObject" in window)) { + var doc = new ActiveXObject('Microsoft.XMLDOM'); + doc.loadXML(str); + return doc; + } + + if (typeof DOMParser != 'undefined') { + return (new DOMParser()).parseFromString(str, 'text/xml'); + } + + return document.createElement('div', null); } /** + * Checks for XML parse error. + * + * @param {xmlDOM} XML DOM. + * @return boolean. + */ +// from http://stackoverflow.com/questions/11563554/how-do-i-detect-xml-parsing-errors-when-using-javascripts-domparser-in-a-cross +geoXML3.isParseError = function(parsedDocument) { + if ((typeof ActiveXObject != 'undefined') || ("ActiveXObject" in window)) + return false; + // parser and parsererrorNS could be cached on startup for efficiency + var p = new DOMParser(), + errorneousParse = p.parseFromString('<', 'text/xml'), + parsererrorNS = errorneousParse.getElementsByTagName("parsererror")[0].namespaceURI; + + if (parsererrorNS === 'http://www.w3.org/1999/xhtml') { + // In PhantomJS the parseerror element doesn't seem to have a special namespace, so we are just guessing here :( + return parsedDocument.getElementsByTagName("parsererror").length > 0; + } + + return parsedDocument.getElementsByTagNameNS(parsererrorNS, 'parsererror').length > 0; +}; + +/** * Fetches a XML document. * * <p>Fetches/parses the given XML URL and passes the parsed document (in a @@ -1433,62 +1605,87 @@ geoXML3.xmlParse = function (str) { * @param {Function(Document)} callback Function to call when the document is processed. */ geoXML3.fetchXML = function (url, callback) { - function timeoutHandler() { callback(); }; - - var xhrFetcher = new Object(); - if (!!geoXML3.fetchers.length) xhrFetcher = geoXML3.fetchers.pop(); - else if (!!window.XMLHttpRequest) xhrFetcher.fetcher = new window.XMLHttpRequest(); // Most browsers - else if (!!window.ActiveXObject) { // Some IE - // the many versions of IE's XML fetchers - var AXOs = [ - 'MSXML2.XMLHTTP.6.0', - 'MSXML2.XMLHTTP.5.0', - 'MSXML2.XMLHTTP.4.0', - 'MSXML2.XMLHTTP.3.0', - 'MSXML2.XMLHTTP', - 'Microsoft.XMLHTTP', - 'MSXML.XMLHTTP' - ]; - for (var i = 0; i < AXOs.length; i++) { - try { xhrFetcher.fetcher = new ActiveXObject(AXOs[i]); break; } - catch(e) { continue; } - } - if (!xhrFetcher.fetcher) { - geoXML3.log('Unable to create XHR object'); - callback(null); - return null; - } - } - - if (!!xhrFetcher.fetcher.overrideMimeType) xhrFetcher.fetcher.overrideMimeType('text/xml'); - xhrFetcher.fetcher.open('GET', url, true); - xhrFetcher.fetcher.onreadystatechange = function () { - if (xhrFetcher.fetcher.readyState === 4) { - // Retrieval complete - if (!!xhrFetcher.xhrtimeout) clearTimeout(xhrFetcher.xhrtimeout); - if (xhrFetcher.fetcher.status >= 400) { - geoXML3.log('HTTP error ' + xhrFetcher.fetcher.status + ' retrieving ' + url); - callback(); - } - // Returned successfully - else { - if (xhrFetcher.fetcher.responseXML) { - // Sometimes IE will get the data, but won't bother loading it as an XML doc - var xmlDoc = xhrFetcher.fetcher.responseXML; - if (xmlDoc && !xmlDoc.documentElement && !xmlDoc.ownerElement) xmlDoc.loadXML(xhrFetcher.fetcher.responseText); - callback(xmlDoc); - } else // handle valid xml sent with wrong MIME type - callback(geoXML3.xmlParse(xhrFetcher.fetcher.responseText)); - } - - // We're done with this fetcher object - geoXML3.fetchers.push(xhrFetcher); - } - }; - - xhrFetcher.xhrtimeout = setTimeout(timeoutHandler, 60000); - xhrFetcher.fetcher.send(null); - return null; + function timeoutHandler() { callback(); }; + + var xhrFetcher = new Object(); + if (!!geoXML3.fetchers.length) xhrFetcher = geoXML3.fetchers.pop(); + else if (!!window.XMLHttpRequest) xhrFetcher.fetcher = new window.XMLHttpRequest(); // Most browsers + else if (!!window.ActiveXObject) { // Some IE + // the many versions of IE's XML fetchers + var AXOs = [ + 'MSXML2.XMLHTTP.6.0', + 'MSXML2.XMLHTTP.5.0', + 'MSXML2.XMLHTTP.4.0', + 'MSXML2.XMLHTTP.3.0', + 'MSXML2.XMLHTTP', + 'Microsoft.XMLHTTP', + 'MSXML.XMLHTTP' + ]; + for (var i = 0; i < AXOs.length; i++) { + try { xhrFetcher.fetcher = new ActiveXObject(AXOs[i]); break; } + catch(e) { continue; } + } + if (!xhrFetcher.fetcher) { + geoXML3.log('Unable to create XHR object'); + callback(null); + return null; + } + } + + xhrFetcher.fetcher.open('GET', url, true); + if (!!xhrFetcher.fetcher.overrideMimeType) xhrFetcher.fetcher.overrideMimeType('text/xml'); + xhrFetcher.fetcher.onreadystatechange = function () { + if (xhrFetcher.fetcher.readyState === 4) { + // Retrieval complete + if (!!xhrFetcher.xhrtimeout) clearTimeout(xhrFetcher.xhrtimeout); + if (xhrFetcher.fetcher.status >= 400) { + geoXML3.log('HTTP error ' + xhrFetcher.fetcher.status + ' retrieving ' + url); + callback(); + } + // Returned successfully + else { + if (xhrFetcher.fetcher.responseXML) { + // Sometimes IE will get the data, but won't bother loading it as an XML doc + var xml = xhrFetcher.fetcher.responseXML; + if (xml && !xml.documentElement && !xml.ownerElement) { + xml.loadXML(xhrFetcher.fetcher.responseText); + } + } else {// handle valid xml sent with wrong MIME type + xml=geoXML3.xmlParse(xhrFetcher.fetcher.responseText); + } + // handle parse errors + if (xml.parseError && (xml.parseError.errorCode != 0)) { + geoXML3.log("XML parse error "+xml.parseError.errorCode+", "+xml.parseError.reason+"\nLine:"+xml.parseError.line+", Position:"+xml.parseError.linepos+", srcText:"+xml.parseError.srcText); + xml = "failed parse" + } else if (geoXML3.isParseError(xml)) { + geoXML3.log("XML parse error"); + xml = "failed parse" + } + callback(xml); + } + // We're done with this fetcher object + geoXML3.fetchers.push(xhrFetcher); + } + }; + + xhrFetcher.xhrtimeout = setTimeout(timeoutHandler, 60000); + xhrFetcher.fetcher.send(null); + return null; +}; + +var IEversion = function() { + // http://msdn.microsoft.com/workshop/author/dhtml/overview/browserdetection.asp + // Returns the version of Internet Explorer or a -1 + // (indicating the use of another browser). + var rv = -1; // Return value assumes failure + if (navigator.appName == 'Microsoft Internet Explorer') { + var ua = navigator.userAgent; + var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); + if (re.exec(ua) != null) { + rv = parseFloat( RegExp.$1 ); + } + } + return rv; }; /** @@ -1507,117 +1704,131 @@ geoXML3.fetchXML = function (url, callback) { * @see http://code.google.com/apis/kml/documentation/kmzarchives.html */ geoXML3.fetchZIP = function (url, callback, parser) { - // Just need a single 'new' declaration with a really long function... - var zipFile = new ZipFile(url, function (zip) { - // Retrieval complete - - // Check for ERRORs in zip.status - for (var i = 0; i < zip.status.length; i++) { - var msg = zip.status[i]; - if (msg.indexOf("ERROR") == 0) { - geoXML3.log('HTTP/ZIP error retrieving ' + url + ': ' + msg); - callback(); - return; - } - else if (msg.indexOf("WARNING") == 0) { // non-fatal, but still might be useful - geoXML3.log('HTTP/ZIP warning retrieving ' + url + ': ' + msg); - } - } - - // Make sure KMZ structure is according to spec (with a single KML file in the root dir) - var KMLCount = 0; - var KML; - for (var i = 0; i < zip.entries.length; i++) { - var name = zip.entries[i].name; - if (!/\.kml$/.test(name)) continue; - - KMLCount++; - if (KMLCount == 1) KML = i; - else { - geoXML3.log('KMZ warning retrieving ' + url + ': found extra KML "' + name + '" in KMZ; discarding...'); - } - } - - // Returned successfully, but still needs extracting - var baseUrl = cleanURL(defileURL(url), url) + '/'; - var kmlProcessing = { // this is an object just so it gets passed properly - timer: null, - extractLeft: 0, - timerCalls: 0 - }; - var extractCb = function(entry, entryContent) { - var mdUrl = cleanURL(baseUrl, entry.name); - var ext = entry.name.substring(entry.name.lastIndexOf(".") + 1).toLowerCase(); - kmlProcessing.extractLeft--; - - if ((typeof entryContent.description == "string") && (entryContent.name == "Error")) { - geoXML3.log('KMZ error extracting ' + mdUrl + ': ' + entryContent.description); - callback(); - return; - } - - // MIME types that can be used in KML - var mime; - if (ext === 'jpg') ext = 'jpeg'; - if (/^(gif|jpeg|png)$/.test(ext)) mime = 'image/' + ext; - else if (ext === 'mp3') mime = 'audio/mpeg'; - else if (ext === 'm4a') mime = 'audio/mp4'; - else if (ext === 'm4a') mime = 'audio/MP4-LATM'; - else mime = 'application/octet-stream'; - - parser.kmzMetaData[mdUrl] = {}; - parser.kmzMetaData[mdUrl].entry = entry; - // data:image/gif;base64,R0lGODlhEAAOALMA... - parser.kmzMetaData[mdUrl].dataUrl = 'data:' + mime + ';base64,' + base64Encode(entryContent); - - // IE cannot handle GET requests beyond 2071 characters, even if it's an inline image - if (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent) && parser.kmzMetaData[mdUrl].dataUrl.length > 2071) - parser.kmzMetaData[mdUrl].dataUrl = - // this is a simple IE icon; to hint at the problem... - 'data:image/gif;base64,R0lGODlhDwAQAOMPADBPvSpQ1Dpoyz1p6FhwvU2A6ECP63CM04CWxYCk+V6x+UK++Jao3rvC3fj7+v///yH5BAEKAA8ALAAAAAAPABAAAASC8Mk5mwCAUMlWwcLRHEelLA' + - 'oGDMgzSsiyGCAhCETDPMh5XQCBwYBrNBIKWmg0MCQHj8MJU5IoroYCY6AAAgrDIbbQDGIK6DR5UPhlNo0JAlSUNAiDgH7eNAxEDWAKCQM2AAFheVxYAA0AIkFOJ1gBcQQaUQKKA5w7LpcEBwkJaKMUEQA7'; - }; - var kmlExtractCb = function(entry, entryContent) { - if ((typeof entryContent.description == "string") && (entryContent.name == "Error")) { - geoXML3.log('KMZ error extracting ' + mdUrl + ': ' + entryContent.description); - callback(); - return; - } - - // check to see if the KML is the last file extracted - clearTimeout(kmlProcessing.timer); - if (kmlProcessing.extractLeft <= 1) { - kmlProcessing.extractLeft--; - callback(geoXML3.xmlParse(entryContent)); - return; - } - else { - // KML file isn't last yet; it may need to use those files, so wait a bit (100ms) - kmlProcessing.timerCalls++; - if (kmlProcessing.timerCalls < 100) { - kmlProcessing.timer = setTimeout(function() { kmlExtractCb(entry, entryContent); }, 100); - } - else { - geoXML3.log('KMZ warning extracting ' + url + ': entire ZIP has not been extracted after 10 seconds; running through KML, anyway...'); - kmlProcessing.extractLeft--; - callback(geoXML3.xmlParse(entryContent)); - } - } - return; - }; - for (var i = 0; i < zip.entries.length; i++) { - var entry = zip.entries[i]; - var ext = entry.name.substring(entry.name.lastIndexOf(".") + 1).toLowerCase(); - if (!/^(gif|jpe?g|png|kml)$/.test(ext)) continue; // not going to bother to extract files we don't support - if (ext === "kml" && i != KML) continue; // extra KMLs get discarded - if (!parser && ext != "kml") continue; // cannot store images without a parser object - - // extract asynchronously - kmlProcessing.extractLeft++; - if (ext === "kml") entry.extract(kmlExtractCb); - else entry.extract(extractCb); - } - }); + // Just need a single 'new' declaration with a really long function... + var zipFile = new ZipFile(url, function (zip) { + // Retrieval complete + + // Check for ERRORs in zip.status + for (var i = 0; i < zip.status.length; i++) { + var msg = zip.status[i]; + if (msg.indexOf("ERROR") == 0) { + geoXML3.log('HTTP/ZIP error retrieving ' + url + ': ' + msg); + callback(); + return; + } + else if (msg.indexOf("EXCEPTION") == 0) { + geoXML3.log('HTTP/ZIP exception retrieving ' + url + ': ' + msg); + callback(); + return; + } else if (msg.indexOf("WARNING") == 0) { // non-fatal, but still might be useful + geoXML3.log('HTTP/ZIP warning retrieving ' + url + ': ' + msg); + } else if (msg.indexOf("INFO") == 0) { // non-fatal, but still might be useful + geoXML3.log('HTTP/ZIP info retrieving ' + url + ': ' + msg); + } + } + + // Make sure KMZ structure is according to spec (with a single KML file in the root dir) + var KMLCount = 0; + var KML; + for (var i = 0; i < zip.entries.length; i++) { + var name = zip.entries[i].name; + if (!/\.kml$/.test(name)) continue; + + KMLCount++; + if (KMLCount == 1) KML = i; + else { + geoXML3.log('KMZ warning retrieving ' + url + ': found extra KML "' + name + '" in KMZ; discarding...'); + } + } + + // Returned successfully, but still needs extracting + var baseUrl = cleanURL(defileURL(url), url) + '/'; + var kmlProcessing = { // this is an object just so it gets passed properly + timer: null, + extractLeft: 0, + timerCalls: 0 + }; + var extractCb = function(entry, entryContent) { + var mdUrl = cleanURL(baseUrl, entry.name); + var ext = entry.name.substring(entry.name.lastIndexOf(".") + 1).toLowerCase(); + kmlProcessing.extractLeft--; + + if ((typeof entryContent.description == "string") && (entryContent.name == "Error")) { + geoXML3.log('KMZ error extracting ' + mdUrl + ': ' + entryContent.description); + callback(); + return; + } + + // MIME types that can be used in KML + var mime; + if (ext === 'jpg') ext = 'jpeg'; + if (/^(gif|jpeg|png)$/.test(ext)) mime = 'image/' + ext; + else if (ext === 'mp3') mime = 'audio/mpeg'; + else if (ext === 'm4a') mime = 'audio/mp4'; + else if (ext === 'm4a') mime = 'audio/MP4-LATM'; + else mime = 'application/octet-stream'; + + parser.kmzMetaData[mdUrl] = {}; + parser.kmzMetaData[mdUrl].entry = entry; + // data:image/gif;base64,R0lGODlhEAAOALMA... + parser.kmzMetaData[mdUrl].dataUrl = 'data:' + mime + ';base64,' + base64Encode(entryContent); + // IE cannot handle GET requests beyond 2071 characters, even if it's an inline image + if (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) + { + if (((IEversion() < 8.0) && + (parser.kmzMetaData[mdUrl].dataUrl.length > 2071)) || + ((IEversion < 9.0) && + (parser.kmzMetaData[mdUrl].dataUrl.length > 32767))) { + parser.kmzMetaData[mdUrl].dataUrl = + // this is a simple IE icon; to hint at the problem... + 'data:image/gif;base64,R0lGODlhDwAQAOMPADBPvSpQ1Dpoyz1p6FhwvU2A6ECP63CM04CWxYCk+V6x+UK++Jao3rvC3fj7+v///yH5BAEKAA8ALAAAAAAPABAAAASC8Mk5mwCAUMlWwcLRHEelLA' + + 'oGDMgzSsiyGCAhCETDPMh5XQCBwYBrNBIKWmg0MCQHj8MJU5IoroYCY6AAAgrDIbbQDGIK6DR5UPhlNo0JAlSUNAiDgH7eNAxEDWAKCQM2AAFheVxYAA0AIkFOJ1gBcQQaUQKKA5w7LpcEBwkJaKMUEQA7'; + } + } + parser.kmzMetaData[internalSrc(entry.name)]=parser.kmzMetaData[mdUrl]; + + }; + var kmlExtractCb = function(entry, entryContent) { + if ((typeof entryContent.description == "string") && (entryContent.name == "Error")) { + geoXML3.log('KMZ error extracting ' + entry.name + ': ' + entryContent.description); + callback(); + return; + } + + // check to see if the KML is the last file extracted + clearTimeout(kmlProcessing.timer); + if (kmlProcessing.extractLeft <= 1) { + kmlProcessing.extractLeft--; + callback(geoXML3.xmlParse(entryContent)); + return; + } + else { + // KML file isn't last yet; it may need to use those files, so wait a bit (100ms) + kmlProcessing.timerCalls++; + if (kmlProcessing.timerCalls < 100) { + kmlProcessing.timer = setTimeout(function() { kmlExtractCb(entry, entryContent); }, 100); + } + else { + geoXML3.log('KMZ warning extracting ' + url + ': entire ZIP has not been extracted after 10 seconds; running through KML, anyway...'); + kmlProcessing.extractLeft--; + callback(geoXML3.xmlParse(entryContent)); + } + } + return; + }; + for (var i = 0; i < zip.entries.length; i++) { + var entry = zip.entries[i]; + var ext = entry.name.substring(entry.name.lastIndexOf(".") + 1).toLowerCase(); + if (!/^(gif|jpe?g|png|kml)$/.test(ext)) continue; // not going to bother to extract files we don't support + if (ext === "kml" && i != KML) continue; // extra KMLs get discarded + if (!parser && ext != "kml") continue; // cannot store images without a parser object + + // extract asynchronously + kmlProcessing.extractLeft++; + if (ext === "kml") entry.extract(kmlExtractCb); + else entry.extract(extractCb); + } + }); //,3 for most verbose logging }; @@ -1629,18 +1840,18 @@ geoXML3.fetchZIP = function (url, callback, parser) { * @return {String|Null} */ geoXML3.nodeValue = function(node, defVal) { - var retStr=""; - if (!node) { - return (typeof defVal === 'undefined' || defVal === null) ? null : defVal; - } - if(node.nodeType==3||node.nodeType==4||node.nodeType==2){ - retStr+=node.nodeValue; - }else if(node.nodeType==1||node.nodeType==9||node.nodeType==11){ - for(var i=0;i<node.childNodes.length;++i){ - retStr+=arguments.callee(node.childNodes[i]); - } - } - return retStr; + var retStr=""; + if (!node) { + return (typeof defVal === 'undefined' || defVal === null) ? null : defVal; + } + if(node.nodeType==3||node.nodeType==4||node.nodeType==2){ + retStr+=node.nodeValue; + }else if(node.nodeType==1||node.nodeType==9||node.nodeType==11){ + for(var i=0;i<node.childNodes.length;++i){ + retStr+=arguments.callee(node.childNodes[i]); + } + } + return retStr; }; /** @@ -1651,12 +1862,12 @@ geoXML3.nodeValue = function(node, defVal) { * @return {Boolean|Null} */ geoXML3.getBooleanValue = function(node, defVal) { - var nodeContents = geoXML3.nodeValue(node); - if (nodeContents === null) return defVal || false; - nodeContents = parseInt(nodeContents); - if (isNaN(nodeContents)) return true; - if (nodeContents == 0) return false; - else return true; + var nodeContents = geoXML3.nodeValue(node); + if (nodeContents === null) return defVal || false; + nodeContents = parseInt(nodeContents); + if (isNaN(nodeContents)) return true; + if (nodeContents == 0) return false; + else return true; } /** @@ -1671,32 +1882,32 @@ geoXML3.getBooleanValue = function(node, defVal) { * @author Brendan Byrd */ geoXML3.getElementsByTagNameNS = function(node, namespace, tagname) { - if (node && typeof node.getElementsByTagNameNS != 'undefined') return node.getElementsByTagNameNS(namespace, tagname); - if (!node) return []; - - var root = node.documentElement || node.ownerDocument && node.ownerDocument.documentElement; - if (!root || !root.attributes) return []; - - // search for namespace prefix - for (var i = 0; i < root.attributes.length; i++) { - var attr = root.attributes[i]; - if (attr.prefix === 'xmlns' && attr.nodeValue === namespace) return node.getElementsByTagName(attr.baseName + ':' + tagname); - else if (attr.nodeName === 'xmlns' && attr.nodeValue === namespace) { - // default namespace - if (typeof node.selectNodes != 'undefined') { - // Newer IEs have the SelectionNamespace property that can be used with selectNodes - if (!root.ownerDocument.getProperty('SelectionNamespaces')) - root.ownerDocument.setProperty('SelectionNamespaces', "xmlns:defaultNS='" + namespace + "'"); - return node.selectNodes('.//defaultNS:' + tagname); - } - else { - // Otherwise, you can still try to tack on the 'xmlns' attribute to root - root.setAttribute('xmlns:defaultNS', namespace); - return node.getElementsByTagName('defaultNS:' + tagname); - } - } - } - return geoXML3.getElementsByTagName(node, tagname); // try the unqualified version + if (node && typeof node.getElementsByTagNameNS != 'undefined') return node.getElementsByTagNameNS(namespace, tagname); + if (!node) return []; + + var root = node.documentElement || node.ownerDocument && node.ownerDocument.documentElement; + if (!root || !root.attributes) return []; + + // search for namespace prefix + for (var i = 0; i < root.attributes.length; i++) { + var attr = root.attributes[i]; + if (attr.prefix === 'xmlns' && attr.nodeValue === namespace) return node.getElementsByTagName(attr.baseName + ':' + tagname); + else if (attr.nodeName === 'xmlns' && attr.nodeValue === namespace) { + // default namespace + if (typeof node.selectNodes != 'undefined') { + // Newer IEs have the SelectionNamespace property that can be used with selectNodes + if (!root.ownerDocument.getProperty('SelectionNamespaces')) + root.ownerDocument.setProperty('SelectionNamespaces', "xmlns:defaultNS='" + namespace + "'"); + return node.selectNodes('.//defaultNS:' + tagname); + } + else { + // Otherwise, you can still try to tack on the 'xmlns' attribute to root + root.setAttribute('xmlns:defaultNS', namespace); + return node.getElementsByTagName('defaultNS:' + tagname); + } + } + } + return geoXML3.getElementsByTagName(node, tagname); // try the unqualified version }; /** @@ -1711,9 +1922,9 @@ geoXML3.getElementsByTagNameNS = function(node, namespace, tagname) { * @author Brendan Byrd */ geoXML3.getElementsByTagName = function(node, tagname) { - if (node && typeof node.getElementsByTagNameNS != 'undefined') return node.getElementsByTagName(tagname); // if it has both functions, it should be accurate + if (node && typeof node.getElementsByTagNameNS != 'undefined') return node.getElementsByTagName(tagname); // if it has both functions, it should be accurate // if (node && typeof node.selectNodes != 'undefined') return node.selectNodes(".//*[local-name()='" + tagname + "']"); - return node.getElementsByTagName(tagname); // hope for the best... + return node.getElementsByTagName(tagname); // hope for the best... } /** @@ -1726,21 +1937,30 @@ geoXML3.getElementsByTagName = function(node, tagname) { * @author Brendan Byrd */ var toAbsURL = function (d, s) { - var p, f, i; - var h = location.protocol + "://" + location.host; + var p, f, i; + var h = location.protocol + "://" + location.host; + + if (!s.length) return ''; + if (/^\w+:/.test(s)) return s; + if (s.indexOf('/') == 0) return h + s; - if (!s.length) return ''; - if (/^\w+:/.test(s)) return s; - if (s.indexOf('/') == 0) return h + s; + p = d.replace(/\/[^\/]*$/, ''); + f = s.match(/\.\.\//g); + if (f) { + s = s.substring(f.length * 3); + for (i = f.length; i--;) { p = p.substring(0, p.lastIndexOf('/')); } + } - p = d.replace(/\/[^\/]*$/, ''); - f = s.match(/\.\.\//g); - if (f) { - s = s.substring(f.length * 3); - for (i = f.length; i--;) { p = p.substring(0, p.lastIndexOf('/')); } - } + return h + p + '/' + s; +} - return h + p + '/' + s; +var internalSrc = function(src) { + //this gets the full url + var url = document.location.href; + //this removes everything after the last slash in the path + url = url.substring(0,url.lastIndexOf("/") + 1); + var internalPath= url+src; + return internalPath; } /** @@ -1752,9 +1972,9 @@ var toAbsURL = function (d, s) { * @author Brendan Byrd */ var dehostURL = function (s) { - var h = location.protocol + "://" + location.host; - h = h.replace(/([\.\\\+\*\?\[\^\]\$\(\)])/g, '\\$1'); // quotemeta - return s.replace(new RegExp('^' + h, 'i'), ''); + var h = location.protocol + "://" + location.host; + h = h.replace(/([\.\\\+\*\?\[\^\]\$\(\)])/g, '\\$1'); // quotemeta + return s.replace(new RegExp('^' + h, 'i'), ''); } /** @@ -1782,46 +2002,46 @@ var defileURL = function (s) { return s ? s.substr(0, s.lastIndexOf('/') + 1) // Some extra Array subs for ease of use // http://stackoverflow.com/questions/143847/best-way-to-find-an-item-in-a-javascript-array Array.prototype.hasObject = ( - !Array.indexOf ? function (obj) { - var l = this.length + 1; - while (l--) { - if (this[l - 1] === obj) return true; - } - return false; - } : function (obj) { - return (this.indexOf(obj) !== -1); - } + !Array.indexOf ? function (obj) { + var l = this.length + 1; + while (l--) { + if (this[l - 1] === obj) return true; + } + return false; + } : function (obj) { + return (this.indexOf(obj) !== -1); + } ); Array.prototype.hasItemInObj = function (name, item) { - var l = this.length + 1; - while (l--) { - if (this[l - 1][name] === item) return true; - } - return false; + var l = this.length + 1; + while (l--) { + if (this[l - 1][name] === item) return true; + } + return false; }; if (!Array.prototype.indexOf) { - Array.prototype.indexOf = function (obj, fromIndex) { - if (fromIndex == null) { - fromIndex = 0; - } else if (fromIndex < 0) { - fromIndex = Math.max(0, this.length + fromIndex); - } - for (var i = fromIndex, j = this.length; i < j; i++) { - if (this[i] === obj) return i; - } - return -1; - }; + Array.prototype.indexOf = function (obj, fromIndex) { + if (fromIndex == null) { + fromIndex = 0; + } else if (fromIndex < 0) { + fromIndex = Math.max(0, this.length + fromIndex); + } + for (var i = fromIndex, j = this.length; i < j; i++) { + if (this[i] === obj) return i; + } + return -1; + }; } Array.prototype.indexOfObjWithItem = function (name, item, fromIndex) { - if (fromIndex == null) { - fromIndex = 0; - } else if (fromIndex < 0) { - fromIndex = Math.max(0, this.length + fromIndex); - } - for (var i = fromIndex, j = this.length; i < j; i++) { - if (this[i][name] === item) return i; - } - return -1; + if (fromIndex == null) { + fromIndex = 0; + } else if (fromIndex < 0) { + fromIndex = Math.max(0, this.length + fromIndex); + } + for (var i = fromIndex, j = this.length; i < j; i++) { + if (this[i][name] === item) return i; + } + return -1; }; /** @@ -1833,23 +2053,23 @@ Array.prototype.indexOfObjWithItem = function (name, item, fromIndex) { * @author Brendan Byrd */ var base64Encode = function(input) { - var keyString = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - var output = ""; - var chr1, chr2, chr3, enc1, enc2, enc3, enc4; - var i = 0; - while (i < input.length) { - chr1 = input[i++]; - chr2 = input[i++]; - chr3 = input[i++]; - enc1 = chr1 >> 2; - enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); - enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); - enc4 = chr3 & 63; - - if (chr2 == undefined) enc3 = enc4 = 64; - else if (chr3 == undefined) enc4 = 64; - - output = output + keyString.charAt(enc1) + keyString.charAt(enc2) + keyString.charAt(enc3) + keyString.charAt(enc4); - } - return output; + var keyString = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + var output = ""; + var chr1, chr2, chr3, enc1, enc2, enc3, enc4; + var i = 0; + while (i < input.length) { + chr1 = input[i++]; + chr2 = input[i++]; + chr3 = input[i++]; + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (chr2 == undefined) enc3 = enc4 = 64; + else if (chr3 == undefined) enc4 = 64; + + output = output + keyString.charAt(enc1) + keyString.charAt(enc2) + keyString.charAt(enc3) + keyString.charAt(enc4); + } + return output; }; diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/ext.sm.googlemaps3ajax.js b/www/wiki/extensions/Maps/resources/GoogleMaps/googlemaps3ajax.js index 9f8c658a..aa86319e 100644 --- a/www/wiki/extensions/Maps/resources/GoogleMaps/ext.sm.googlemaps3ajax.js +++ b/www/wiki/extensions/Maps/resources/GoogleMaps/googlemaps3ajax.js @@ -17,7 +17,7 @@ if( typeof google === 'undefined' ) { return; } - $( window.maps.googlemapsList ).each( function( index, map ) { + $( window.mapsGoogleList ).each( function( index, map ) { if( !map.options.ajaxquery || !map.options.ajaxcoordproperty ) { return; } diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/jquery.googlemap.js b/www/wiki/extensions/Maps/resources/GoogleMaps/jquery.googlemap.js index d0317d16..d76460d5 100644 --- a/www/wiki/extensions/Maps/resources/GoogleMaps/jquery.googlemap.js +++ b/www/wiki/extensions/Maps/resources/GoogleMaps/jquery.googlemap.js @@ -233,7 +233,9 @@ var geoXml = new geoXML3.parser({ map:_this.map, zoom:options.kmlrezoom, - failedParse:function(){ + failedParse:function(document){ + console.log(options.kml); + console.log(document); alert(mw.msg('maps-kml-parsing-failed')); } }); @@ -454,7 +456,7 @@ }; this.createMarkerCluster = function() { - if ( !options.markercluster ) { + if ( !options.cluster ) { return; } if (this.markercluster) { @@ -751,7 +753,7 @@ //Add custom controls // - Fullscreen - if(options.enablefullscreen){ + if(options.fullscreen){ this.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(new FullscreenControl(this.map)); } }; @@ -886,7 +888,7 @@ //Complete path to OpenLayers WMS layer - if (!options.markercluster) { + if (!options.cluster) { this.setup(); } else { mw.loader.using( 'ext.maps.gm3.markercluster', function() { |