Strabon

changeset 542:4f97fb6389a5

added support for viewing a KML in Strabon endpoint when in localhost (using geoxml3)
Fixes Ticket #8: http://bug.strabon.di.uoa.gr/ticket/8
author Babis Nikolaou <charnik@di.uoa.gr>
date Fri Sep 14 13:03:24 2012 +0300 (2012-09-14)
parents f263e457fb35
children f7e99907a8f1 4657ea5fa82d
files endpoint/WebContent/js/ProjectedOverlay.js endpoint/WebContent/js/ZipFile.complete.js endpoint/WebContent/js/geoxml3-kmz.js endpoint/WebContent/query.jsp endpoint/pom.xml endpoint/src/main/java/eu/earthobservatory/org/StrabonEndpoint/QueryBean.java
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/endpoint/WebContent/js/ProjectedOverlay.js	Fri Sep 14 13:03:24 2012 +0300
     1.3 @@ -0,0 +1,138 @@
     1.4 +// Create an overlay on the map from a projected image - Maps v3...
     1.5 +// Author. John D. Coryat 05/2009
     1.6 +// USNaviguide LLC - http://www.usnaviguide.com
     1.7 +// Thanks go to Mile Williams EInsert: http://econym.googlepages.com/einsert.js, Google's GOverlay Example and Bratliff's suggestion...
     1.8 +// Opacity code from TPhoto: http://gmaps.tommangan.us/addtphoto.html
     1.9 +// This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
    1.10 +//
    1.11 +// Parameters:
    1.12 +//    map: This Map
    1.13 +//    imageUrl: URL of the image (Mandatory)
    1.14 +//    bounds: Bounds object of image destination (Mandatory)
    1.15 +//    Options:
    1.16 +//    addZoom: Added Zoom factor as a parameter to the imageUrl (include complete parameter, including separater like '?zoom='
    1.17 +//    percentOpacity: Default 50, percent opacity to use when the image is loaded 0-100.
    1.18 +//    id: Default imageUrl, ID of the div
    1.19 +//
    1.20 +	
    1.21 +function ProjectedOverlay(map, imageUrl, bounds, opts)
    1.22 +{
    1.23 + google.maps.OverlayView.call(this);
    1.24 +
    1.25 + this.map_ = map;
    1.26 + this.url_ = imageUrl ;
    1.27 + this.bounds_ = bounds ;
    1.28 + this.addZ_ = opts.addZoom || '' ;				// Add the zoom to the image as a parameter
    1.29 + this.id_ = opts.id || this.url_ ;				// Added to allow for multiple images
    1.30 + this.percentOpacity_ = opts.percentOpacity || 50 ;
    1.31 +
    1.32 + this.setMap(map);
    1.33 +}
    1.34 +
    1.35 +ProjectedOverlay.prototype = new google.maps.OverlayView();
    1.36 +
    1.37 +ProjectedOverlay.prototype.createElement = function()
    1.38 +{
    1.39 + var panes = this.getPanes() ;
    1.40 + var div = this.div_ ;
    1.41 +
    1.42 + if (!div)
    1.43 + {
    1.44 +  div = this.div_ = document.createElement("div");
    1.45 +  div.style.position = "absolute" ;
    1.46 +  div.setAttribute('id',this.id_) ;
    1.47 +  this.div_ = div ;
    1.48 +  this.lastZoom_ = -1 ;
    1.49 +  if( this.percentOpacity_ )
    1.50 +  {
    1.51 +   this.setOpacity(this.percentOpacity_) ;
    1.52 +  }
    1.53 +  panes.overlayLayer.appendChild(div);
    1.54 + }
    1.55 +}
    1.56 +
    1.57 +// Remove the main DIV from the map pane
    1.58 +
    1.59 +ProjectedOverlay.prototype.remove = function()
    1.60 +{
    1.61 + if (this.div_) 
    1.62 + {
    1.63 +  this.div_.parentNode.removeChild(this.div_);
    1.64 +  this.div_ = null;
    1.65 + }
    1.66 +}
    1.67 +
    1.68 +// Redraw based on the current projection and zoom level...
    1.69 +
    1.70 +ProjectedOverlay.prototype.draw = function(firstTime)
    1.71 +{
    1.72 + // Creates the element if it doesn't exist already.
    1.73 +
    1.74 + this.createElement();
    1.75 +
    1.76 + if (!this.div_)
    1.77 + {
    1.78 +  return ;
    1.79 + }
    1.80 +
    1.81 + var c1 = this.get('projection').fromLatLngToDivPixel(this.bounds_.getSouthWest());
    1.82 + var c2 = this.get('projection').fromLatLngToDivPixel(this.bounds_.getNorthEast());
    1.83 +
    1.84 + if (!c1 || !c2) return;
    1.85 +
    1.86 + // Now position our DIV based on the DIV coordinates of our bounds
    1.87 +
    1.88 + this.div_.style.width = Math.abs(c2.x - c1.x) + "px";
    1.89 + this.div_.style.height = Math.abs(c2.y - c1.y) + "px";
    1.90 + this.div_.style.left = Math.min(c2.x, c1.x) + "px";
    1.91 + this.div_.style.top = Math.min(c2.y, c1.y) + "px";
    1.92 +
    1.93 + // Do the rest only if the zoom has changed...
    1.94 + 
    1.95 + if ( this.lastZoom_ == this.map_.getZoom() )
    1.96 + {
    1.97 +  return ;
    1.98 + }
    1.99 +
   1.100 + this.lastZoom_ = this.map_.getZoom() ;
   1.101 +
   1.102 + var url = this.url_ ;
   1.103 +
   1.104 + if ( this.addZ_ )
   1.105 + {
   1.106 +  url += this.addZ_ + this.map_.getZoom() ;
   1.107 + }
   1.108 +
   1.109 + this.div_.innerHTML = '<img src="' + url + '"  width=' + this.div_.style.width + ' height=' + this.div_.style.height + ' >' ;
   1.110 +}
   1.111 +
   1.112 +ProjectedOverlay.prototype.setOpacity=function(opacity)
   1.113 +{
   1.114 + if (opacity < 0)
   1.115 + {
   1.116 +  opacity = 0 ;
   1.117 + }
   1.118 + if(opacity > 100)
   1.119 + {
   1.120 +  opacity = 100 ;
   1.121 + }
   1.122 + var c = opacity/100 ;
   1.123 +
   1.124 + if (typeof(this.div_.style.filter) =='string')
   1.125 + {
   1.126 +  this.div_.style.filter = 'alpha(opacity:' + opacity + ')' ;
   1.127 + }
   1.128 + if (typeof(this.div_.style.KHTMLOpacity) == 'string' )
   1.129 + {
   1.130 +  this.div_.style.KHTMLOpacity = c ;
   1.131 + }
   1.132 + if (typeof(this.div_.style.MozOpacity) == 'string')
   1.133 + {
   1.134 +  this.div_.style.MozOpacity = c ;
   1.135 + }
   1.136 + if (typeof(this.div_.style.opacity) == 'string')
   1.137 + {
   1.138 +  this.div_.style.opacity = c ;
   1.139 + }
   1.140 +}
   1.141 +
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/endpoint/WebContent/js/ZipFile.complete.js	Fri Sep 14 13:03:24 2012 +0300
     2.3 @@ -0,0 +1,2172 @@
     2.4 +// ZipFile.complete.js
     2.5 +//
     2.6 +// 2/17/2012
     2.7 +//
     2.8 +// =======================================================
     2.9 +//
    2.10 +
    2.11 +// JSIO.core.js
    2.12 +// ------------------------------------------------------------------
    2.13 +//
    2.14 +// core methods for Javascript IO.
    2.15 +//
    2.16 +// =======================================================
    2.17 +//
    2.18 +// Copyleft (c) 2010, Dino Chiesa via MS-PL
    2.19 +// Copyleft (c) 2012, Brendan Byrd via GPL
    2.20 +//
    2.21 +// This work is licensed under the GPLv3.
    2.22 +
    2.23 +
    2.24 +(function(){
    2.25 +    if (typeof JSIO == "object"){
    2.26 +        var e1 = new Error("JSIO is already defined");
    2.27 +        e1.source = "JSIO.core.js";
    2.28 +        throw e1;
    2.29 +    }
    2.30 +
    2.31 +    JSIO = {};
    2.32 +
    2.33 +    JSIO.version = "2.0 2012Feb";
    2.34 +
    2.35 +    JSIO.throwError = function(msg, source, sub) {
    2.36 +        var error = new Error("Error: " + msg);
    2.37 +        error.source = (source || this._typename || 'JSIO') + (sub ? '.'+sub : '');
    2.38 +        throw error;
    2.39 +    }
    2.40 +
    2.41 +    // Format a number as hex.  Quantities over 7ffffff will be displayed properly.
    2.42 +    JSIO.decimalToHexString = function(number, digits) {
    2.43 +        if (number < 0) {
    2.44 +            number = 0xFFFFFFFF + number + 1;
    2.45 +        }
    2.46 +        var r1 = number.toString(16).toUpperCase();
    2.47 +        if (digits) {
    2.48 +            r1 = "00000000" + r1;
    2.49 +            r1 = r1.substring(r1.length - digits);
    2.50 +        }
    2.51 +        return r1;
    2.52 +    };
    2.53 +
    2.54 +    JSIO.FileType = {
    2.55 +        Text    : 0,
    2.56 +        Binary  : 1,
    2.57 +        XML     : 2,
    2.58 +        Unknown : 3
    2.59 +    };
    2.60 +
    2.61 +
    2.62 +    JSIO.guessFileType = function(name) {
    2.63 +       if (name == "makefile")  { return JSIO.FileType.Text; }
    2.64 +
    2.65 +        var lastDot = name.lastIndexOf(".");
    2.66 +        if (lastDot <= 0) { return JSIO.FileType.Unknown; }
    2.67 +
    2.68 +        var ext= name.substring(lastDot);
    2.69 +        if (ext == ".zip")   { return JSIO.FileType.Binary; }
    2.70 +        if (ext == ".xlsx")  { return JSIO.FileType.Binary; }
    2.71 +        if (ext == ".docx")  { return JSIO.FileType.Binary; }
    2.72 +        if (ext == ".dll")   { return JSIO.FileType.Binary; }
    2.73 +        if (ext == ".obj")   { return JSIO.FileType.Binary; }
    2.74 +        if (ext == ".pdb")   { return JSIO.FileType.Binary; }
    2.75 +        if (ext == ".exe")   { return JSIO.FileType.Binary; }
    2.76 +        if (ext == ".kmz")   { return JSIO.FileType.Binary; }
    2.77 +
    2.78 +        if (ext == ".xml")      { return JSIO.FileType.XML; }
    2.79 +        if (ext == ".xsl")      { return JSIO.FileType.XML; }
    2.80 +        if (ext == ".kml")      { return JSIO.FileType.XML; }
    2.81 +        if (ext == ".csproj")   { return JSIO.FileType.XML; }
    2.82 +        if (ext == ".vbproj")   { return JSIO.FileType.XML; }
    2.83 +        if (ext == ".shfbproj") { return JSIO.FileType.XML; }
    2.84 +        if (ext == ".resx")     { return JSIO.FileType.XML; }
    2.85 +        if (ext == ".xslt")     { return JSIO.FileType.XML; }
    2.86 +
    2.87 +        if (ext == ".sln")  { return JSIO.FileType.Text; }
    2.88 +        if (ext == ".htm")  { return JSIO.FileType.Text; }
    2.89 +        if (ext == ".html") { return JSIO.FileType.Text; }
    2.90 +        if (ext == ".js")   { return JSIO.FileType.Text; }
    2.91 +        if (ext == ".vb")   { return JSIO.FileType.Text; }
    2.92 +        if (ext == ".txt")  { return JSIO.FileType.Text; }
    2.93 +        if (ext == ".rels") { return JSIO.FileType.Text; }
    2.94 +        if (ext == ".css")  { return JSIO.FileType.Text; }
    2.95 +        if (ext == ".cs")   { return JSIO.FileType.Text; }
    2.96 +        if (ext == ".asp")  { return JSIO.FileType.Text; }
    2.97 +
    2.98 +        return JSIO.FileType.Unknown;
    2.99 +    };
   2.100 +
   2.101 +    JSIO.stringOfLength = function (charCode, length) {
   2.102 +        var s3 = "";
   2.103 +        for (var i = 0; i < length; i++) {
   2.104 +            s3 += String.fromCharCode(charCode);
   2.105 +        }
   2.106 +        return s3;
   2.107 +    };
   2.108 +
   2.109 +    JSIO.formatByteArray = function(b) {
   2.110 +        var s1 = "0000  ";
   2.111 +        var s2 = "";
   2.112 +        for (var i = 0; i < b.length; i++) {
   2.113 +            if (i !== 0 && i % 16 === 0) {
   2.114 +                s1 += "    " + s2 +"\n" + JSIO.decimalToHexString(i, 4) + "  ";
   2.115 +                s2 = "";
   2.116 +            }
   2.117 +            s1 += JSIO.decimalToHexString(b[i], 2) + " ";
   2.118 +            if (b[i] >=32 && b[i] <= 126) {
   2.119 +                s2 += String.fromCharCode(b[i]);
   2.120 +            } else {
   2.121 +                s2 += ".";
   2.122 +            }
   2.123 +        }
   2.124 +        if (s2.length > 0) {
   2.125 +            s1 += JSIO.stringOfLength(32, ((i%16>0)? ((16 - i%16) * 3) : 0) + 4) + s2;
   2.126 +        }
   2.127 +        return s1;
   2.128 +    };
   2.129 +
   2.130 +    JSIO.htmlEscape = function(str) {
   2.131 +        return str
   2.132 +            .replace(new RegExp( "&", "g" ), "&amp;")
   2.133 +            .replace(new RegExp( "<", "g" ), "&lt;")
   2.134 +            .replace(new RegExp( ">", "g" ), "&gt;")
   2.135 +            .replace(new RegExp( "\x13", "g" ), "<br/>")
   2.136 +            .replace(new RegExp( "\x10", "g" ), "<br/>");
   2.137 +    };
   2.138 +
   2.139 +    JSIO.massApply = function(func, funcThis, arr, needReturn) {
   2.140 +        var arrayLimit = 99999;  // Chrome has an apply/array limit of 99999; Firefox = 491519
   2.141 +        if (arr.length < arrayLimit) return func.apply(funcThis, arr);
   2.142 +        else {
   2.143 +            var newThis = funcThis;
   2.144 +            var offset = 0;
   2.145 +            var end    = 99999;
   2.146 +
   2.147 +            while (offset < arr.length) {
   2.148 +                var arrSlice;
   2.149 +                if      (arr.subarray) arrSlice = arr.subarray(offset, end);
   2.150 +                else if (arr.slice)    arrSlice = arr.slice(offset, end);
   2.151 +
   2.152 +                if (needReturn) newThis += func.apply(newThis,  arrSlice);
   2.153 +                else                       func.apply(funcThis, arrSlice);
   2.154 +
   2.155 +                offset += arrayLimit;
   2.156 +                end    += arrayLimit;
   2.157 +                end     = Math.min(arr.length, end);
   2.158 +            }
   2.159 +            return newThis;
   2.160 +        }
   2.161 +    }
   2.162 +
   2.163 +})();
   2.164 +
   2.165 +/// JSIO.core.js ends
   2.166 +
   2.167 +
   2.168 +// JSIO.BasicByteReaders.js
   2.169 +// ------------------------------------------------------------------
   2.170 +//
   2.171 +// Part of the JSIO library.  Adds a couple basic ByteReaders to JSIO.
   2.172 +// ByteReaders are forward-only byte-wise readers. They read one byte at
   2.173 +// a time from a source.
   2.174 +//
   2.175 +// =======================================================
   2.176 +//
   2.177 +// A ByteReader exposes an interface with these functions:
   2.178 +//
   2.179 +//    readByte()
   2.180 +//       must return null when EOF is reached.
   2.181 +//
   2.182 +//    readToEnd()
   2.183 +//       returns an array of all bytes read, to EOF
   2.184 +//
   2.185 +//    beginReadToEnd(callback)
   2.186 +//       async version of the above
   2.187 +//
   2.188 +//    readBytes(n)
   2.189 +//       returns an array of the next n bytes from the source
   2.190 +//
   2.191 +//    beginReadBytes(n, callback)
   2.192 +//       async version of the above
   2.193 +//
   2.194 +// =======================================================
   2.195 +//
   2.196 +// Copyleft (c) 2010, Dino Chiesa via MS-PL
   2.197 +// Copyleft (c) 2012, Brendan Byrd via GPL
   2.198 +//
   2.199 +// This work is licensed under the GPLv3.
   2.200 +
   2.201 +
   2.202 +(function(){
   2.203 +    var version = "2.0 2012Feb";
   2.204 +
   2.205 +    if (typeof JSIO !== "object") { JSIO = {}; }
   2.206 +    if ((typeof JSIO.version !== "string")) {
   2.207 +        JSIO.version = version;
   2.208 +    }
   2.209 +    else if ((JSIO.version.length < 3) ||
   2.210 +            (JSIO.version.substring(0,3) !== "2.0")) {
   2.211 +        JSIO.version += " " + version;
   2.212 +    }
   2.213 +
   2.214 +    // =======================================================
   2.215 +    // the base object, used as the prototype of all ByteReader objects.
   2.216 +    var _byteReaderBase = function () {
   2.217 +        this.position = 0;
   2.218 +        // position must be incremented in .readByte() for all derived classes
   2.219 +    };
   2.220 +
   2.221 +    _byteReaderBase.prototype._throwError = JSIO.throwError;
   2.222 +
   2.223 +    _byteReaderBase.prototype._limitCheck = function(len, startPos) {
   2.224 +        var LOE = {
   2.225 +            len: len,
   2.226 +            pos: startPos,
   2.227 +            end: startPos+len
   2.228 +        };
   2.229 +
   2.230 +        if (len === 0)              return {len:0, pos:0, end:0};
   2.231 +        if (len < 0)                this._throwError("Invalid read length");
   2.232 +        if (!this.length)           return {len:len, pos:this.position, end:len+this.position};
   2.233 +        if (!startPos >= 0)         LOE.pos = this.position;
   2.234 +        if (this.length <= LOE.pos) this._throwError("EOF reached");
   2.235 +
   2.236 +        LOE.end = LOE.pos+len;
   2.237 +        if (this.length < LOE.end)  LOE.end = LOE.pos+(LOE.len = this.length-this.position);
   2.238 +        return LOE;
   2.239 +    }
   2.240 +
   2.241 +    JSIO.SeekOrigin = {
   2.242 +        Begin     : 0,
   2.243 +        Current   : 1,
   2.244 +        End       : 2,
   2.245 +        SEEK_SET  : 0,
   2.246 +        SEEK_CUR  : 1,
   2.247 +        SEEK_END  : 2
   2.248 +    };
   2.249 +
   2.250 +    _byteReaderBase.prototype.seek = function(offset, origin) {
   2.251 +        switch (origin) {
   2.252 +            case JSIO.SeekOrigin.Begin:
   2.253 +                if (offset == this.position) return this.position;
   2.254 +                if (!this.length) {
   2.255 +                    if      (offset <  this.position) this._throwError('Uni-directional stream cannot seek backwards', null, 'seek');
   2.256 +                    else if (offset >  this.position) return this.read(offset - this.position);  // read will limit check
   2.257 +                }
   2.258 +                else {
   2.259 +                    if (this.length < offset) this._throwError('Cannot seek past reader length', null, 'seek');
   2.260 +                    this.position = offset;
   2.261 +                }
   2.262 +                break;
   2.263 +            case JSIO.SeekOrigin.Current:
   2.264 +                return this.seek(this.position + offset, JSIO.SeekOrigin.Begin);
   2.265 +                break;
   2.266 +            case JSIO.SeekOrigin.End:
   2.267 +                if (!this.length) this._throwError('Uni-directional stream has no known end length for seek', null, 'seek');
   2.268 +                return this.seek(this.length - 1 + offset, JSIO.SeekOrigin.Begin);
   2.269 +                break;
   2.270 +            default:
   2.271 +                this._throwError('Invalid seek method', null, 'seek');
   2.272 +                break;
   2.273 +        }
   2.274 +
   2.275 +        return this.position;
   2.276 +    };
   2.277 +
   2.278 +    _byteReaderBase.prototype.read = function(len, startPos) {
   2.279 +        var LOE = this._limitCheck(len, startPos);
   2.280 +        if (LOE.len === 0) return [];
   2.281 +        if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin);
   2.282 +
   2.283 +        var bytesRead = [];
   2.284 +
   2.285 +        // Faster methods with an array or stream
   2.286 +        if      (this.array && this.array.subarray) bytesRead = this.array.subarray(LOE.pos, LOE.end);
   2.287 +        else if (this.array && this.array.slice)    bytesRead = this.array.slice(LOE.pos, LOE.end);
   2.288 +        else if (this.stream)                       bytesRead = this.stream.read(LOE.len, LOE.pos);
   2.289 +        else if (this.length) {  // Random-access stream
   2.290 +            for(var i=LOE.pos; i<LOE.end; i++) { bytesRead.push(this.readByteAt(i)); }
   2.291 +        }
   2.292 +        else {                   // Uni-directional stream
   2.293 +            for(var i=LOE.pos; i<LOE.end; i++) {
   2.294 +                var b = this.readByte();
   2.295 +                if (b === null || b === undefined) break;
   2.296 +                bytesRead.push(b);
   2.297 +            }
   2.298 +        }
   2.299 +        this.position = LOE.end;
   2.300 +        return bytesRead;
   2.301 +    };
   2.302 +
   2.303 +    _byteReaderBase.prototype.beginRead = function(len, startPos, callback) {
   2.304 +        var LOE = this._limitCheck(len, startPos);
   2.305 +        if (LOE.len === 0) return setTimeout(function() { callback([]); }, 1);
   2.306 +        if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin);
   2.307 +
   2.308 +        var bytesRead = [];
   2.309 +        var thisReader = this;
   2.310 +        var leftToRead = LOE.len;
   2.311 +
   2.312 +        var readBatchAsync = function() {
   2.313 +            var c = 0;
   2.314 +            var pos = thisReader.position;
   2.315 +
   2.316 +            // read a 32k batch
   2.317 +            var l = (leftToRead >= 32768) ? 32768 : leftToRead;
   2.318 +            var newBytes = thisReader.read(l);
   2.319 +            JSIO.massApply(bytesRead.push, bytesRead, newBytes);
   2.320 +            c += l;
   2.321 +            leftToRead -= l;
   2.322 +            if (newBytes.length < l) leftToRead = 0;
   2.323 +
   2.324 +            if (leftToRead>0) setTimeout(readBatchAsync, 1);
   2.325 +            else              callback(bytesRead);
   2.326 +        };
   2.327 +
   2.328 +        // kickoff
   2.329 +        setTimeout(readBatchAsync, 1);  // always async, in ALL situations
   2.330 +        return null;
   2.331 +    };
   2.332 +
   2.333 +    _byteReaderBase.prototype.readToEnd = function() {
   2.334 +        if      (this.array && this.array.subarray) return this.array.subarray(this.position);
   2.335 +        else if (this.array && this.array.slice)    return this.array.slice(this.position);
   2.336 +        else if (this.length)                       return this.read(this.length - this.position);
   2.337 +        else                                        return this.read(9000 * 9000);  // over 9000
   2.338 +    };
   2.339 +
   2.340 +    _byteReaderBase.prototype.beginReadToEnd = function(callback) {
   2.341 +        if      (this.array && this.array.subarray) setTimeout(function() { callback( this.array.subarray(this.position) ); }, 1);
   2.342 +        else if (this.array && this.array.slice)    setTimeout(function() { callback(    this.array.slice(this.position) ); }, 1);
   2.343 +        else if (this.length)                       return this.beginRead(this.length - this.position, this.position, callback);
   2.344 +        else                                        return this.beginRead(9000 * 9000, this.position, callback);
   2.345 +    };
   2.346 +
   2.347 +    // Generic routines; one of these two MUST be overloaded (preferrably both)
   2.348 +    _byteReaderBase.prototype.readByte = function(){
   2.349 +        if (this.length && this.position >= this.length) return null;  // EOF
   2.350 +
   2.351 +        var byte;
   2.352 +        if      (this.array)  byte = this.array[this.position++];
   2.353 +        else if (this.length) byte = this.readByteAt(this.position++);
   2.354 +        else if (this.stream) byte = this.stream.read(1)[0];
   2.355 +        else                  byte = this.read(1)[0];
   2.356 +        return (byte === null || byte === undefined) ? null : byte;
   2.357 +    };
   2.358 +    _byteReaderBase.prototype.readByteAt = function(i) {
   2.359 +        var pos  = this.position;  // no position changes on this one
   2.360 +        if (i === null || i === undefined) i = this.position;
   2.361 +
   2.362 +        var byte;
   2.363 +        if      (this.array)  byte = this.array[i];
   2.364 +        else if (i === pos)   byte = this.readByte();
   2.365 +        else if (this.stream) byte = this.stream.read(1, i)[0];
   2.366 +        else                  byte = this.read(1, i)[0];
   2.367 +
   2.368 +        this.position = pos;
   2.369 +        return (byte === null || byte === undefined) ? null : byte;
   2.370 +    }
   2.371 +
   2.372 +    _byteReaderBase.prototype.readBytes = _byteReaderBase.prototype.read;
   2.373 +    _byteReaderBase.prototype.beginReadBytes = function(len, callback) { return this.beginRead(len, this.position, callback); };
   2.374 +
   2.375 +    _byteReaderBase.prototype.readNumber = function(len, startPos){
   2.376 +        var LOE = this._limitCheck(len, startPos);
   2.377 +        if (LOE.len === 0) LOE.len = 1;
   2.378 +        if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin);
   2.379 +
   2.380 +        var result = 0;
   2.381 +        var bytes  = this.read(LOE.len, LOE.pos);
   2.382 +        for (var i=bytes.length-1; i>=0; i--) {
   2.383 +            // IE only supports 32-bit integer shifting
   2.384 +            //result = result << 8 | bytes[i];
   2.385 +            result = result*256 + bytes[i];
   2.386 +        }
   2.387 +        return result;
   2.388 +    };
   2.389 +
   2.390 +    _byteReaderBase.prototype.readString = function(len, startPos){
   2.391 +        var LOE = this._limitCheck(len, startPos);
   2.392 +        if (LOE.len === 0) return '';
   2.393 +        if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin);
   2.394 +
   2.395 +        var result = '';
   2.396 +        var bytes  = this.read(LOE.len, LOE.pos);
   2.397 +        for(var i=0; i<bytes.length; i++){
   2.398 +            result += String.fromCharCode(bytes[i]);
   2.399 +        }
   2.400 +        return result;
   2.401 +    };
   2.402 +
   2.403 +    _byteReaderBase.prototype.readNullTerminatedString = function(startPos){
   2.404 +        var pos = startPos || this.position;
   2.405 +        if (this.length && this.length < pos) this._throwError('EOF reached', null, 'readNullTerminatedString');
   2.406 +        if (pos != this.position) this.seek(pos, JSIO.SeekOrigin.Begin);
   2.407 +
   2.408 +        var slarge = "";
   2.409 +        var s = "";
   2.410 +        var c = 0;
   2.411 +
   2.412 +        // Faster method with an array
   2.413 +        if (this.array && this.array.indexOf) {
   2.414 +            var len = pos - this.array.indexOf(0, pos);
   2.415 +            if (len > 0) return this.readString(len, pos);
   2.416 +        }
   2.417 +
   2.418 +        var ch;
   2.419 +        while(1) {
   2.420 +            ch = String.fromCharCode(this.readByteAt(pos+c));
   2.421 +            if (ch === null) break;
   2.422 +
   2.423 +            s += ch;
   2.424 +            c++;
   2.425 +            if(c >= 32768) {
   2.426 +                slarge += s;
   2.427 +                s = "";
   2.428 +                pos += c;
   2.429 +                this.position += c;
   2.430 +                c = 0;
   2.431 +            }
   2.432 +        };
   2.433 +        this.position = pos + c;
   2.434 +        return slarge + s;
   2.435 +    };
   2.436 +
   2.437 +    _byteReaderBase.prototype.beginReadNullTerminatedString = function(callback, startPos){
   2.438 +        var pos = startPos || this.position;
   2.439 +        if (this.length && this.length < pos) this._throwError('EOF reached', null, 'beginReadNullTerminatedString');
   2.440 +
   2.441 +        var slarge = "";
   2.442 +        var s = "";
   2.443 +        var thisBinStream = this;
   2.444 +
   2.445 +        var readBatchAsync = function() {
   2.446 +            var c = 0;
   2.447 +
   2.448 +            var ch;
   2.449 +            while(1) {
   2.450 +                ch = String.fromCharCode(this.readByteAt(pos+c));
   2.451 +                if (ch === null) break;
   2.452 +
   2.453 +                s += ch;
   2.454 +                c++;
   2.455 +                if(c >= 32768) {
   2.456 +                    slarge += s;
   2.457 +                    s = "";
   2.458 +                    pos += c;
   2.459 +                    this.position += c;
   2.460 +                    c = 0;
   2.461 +                }
   2.462 +            };
   2.463 +
   2.464 +            thisBinStream.position = pos + c;
   2.465 +            if (ch!==null) setTimeout(readBatchAsync, 1);
   2.466 +            else           callback(slarge+s);
   2.467 +        };
   2.468 +
   2.469 +        // Faster method with an array
   2.470 +        if (this.array && this.array.indexOf) {
   2.471 +            var len = pos - this.array.indexOf(0, pos);
   2.472 +            if (len > 0) readBatchASync = function() { callback(thisBinStream.readString(len, pos)); };
   2.473 +        }
   2.474 +
   2.475 +        // kickoff
   2.476 +        setTimeout(readBatchAsync, 1);  // always async, in ALL situations
   2.477 +        return null;
   2.478 +    };
   2.479 +
   2.480 +
   2.481 +
   2.482 +    JSIO._ByteReaderBase = _byteReaderBase;
   2.483 +    // =======================================================
   2.484 +
   2.485 +
   2.486 +
   2.487 +
   2.488 +    // =======================================================
   2.489 +    // reads from an array of bytes.
   2.490 +    // This basically wraps a readByte() fn onto array access.
   2.491 +    var _arrayReader = function(array) {
   2.492 +        if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.ArrayReader', 'ctor');
   2.493 +        this.position = 0;
   2.494 +        this.array = array;
   2.495 +        this.length = array.length;
   2.496 +        this._typename = "JSIO.ArrayReader";
   2.497 +        this._version = version;
   2.498 +        return this;
   2.499 +    };
   2.500 +
   2.501 +    _arrayReader.prototype = new JSIO._ByteReaderBase();
   2.502 +
   2.503 +    _arrayReader.prototype.readByte = function() {
   2.504 +        if (this.position >= this.array.length) return null;  // EOF
   2.505 +        return this.array[this.position++];
   2.506 +    };
   2.507 +    _arrayReader.prototype.readByteAt = function(i) {
   2.508 +        return this.array[i];
   2.509 +    };
   2.510 +
   2.511 +    // =======================================================
   2.512 +
   2.513 +
   2.514 +    // =======================================================
   2.515 +    // reads bytes at a time from a defined segment of a stream.
   2.516 +    var _streamSegmentReader = function(stream, offset, len) {
   2.517 +        if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.StreamSegmentReader', 'ctor');
   2.518 +        if (!stream)                               this._throwError('You must pass a non-null stream',            'JSIO.StreamSegmentReader', 'ctor');
   2.519 +
   2.520 +        if (!(offset >= 1)) offset = 0;
   2.521 +        if (!(len    >= 1)) len    = 0;
   2.522 +
   2.523 +        this.stream    = stream;
   2.524 +        this.array     = null;
   2.525 +        if (stream.array) {
   2.526 +            var end = len ? offset + len : null;
   2.527 +            if      (stream.array.subarray) this.array = stream.array.subarray(offset, end);
   2.528 +            else if (stream.array.slice)    this.array = stream.array.slice(offset, end);
   2.529 +        }
   2.530 +        this.length    = this.array ? this.array.length : (stream.length ? stream.length - offset : null);
   2.531 +        this.offset    = offset;
   2.532 +        this.limit     = len;
   2.533 +        this.position  = 0;
   2.534 +        this._typeName = 'JSIO.StreamSegmentReader';
   2.535 +        this._version  = version;
   2.536 +
   2.537 +        if (this.array) {
   2.538 +            this.readByte   = _arrayReader.prototype.readByte;
   2.539 +            this.readByteAt = _arrayReader.prototype.readByteAt;
   2.540 +        }
   2.541 +        return this;
   2.542 +    };
   2.543 +
   2.544 +    _streamSegmentReader.prototype = new JSIO._ByteReaderBase();
   2.545 +
   2.546 +    _streamSegmentReader.prototype.readByte = function() {
   2.547 +        if (this.limit && this.position >= this.limit) return null;  // EOF
   2.548 +        this.position++;
   2.549 +        return this.stream.readByteAt(this.offset + this.position - 1);
   2.550 +    };
   2.551 +    _streamSegmentReader.prototype.readByteAt = function(i) {
   2.552 +        if (this.limit && i >= this.limit) return null;  // EOF
   2.553 +        return this.stream.readByteAt(this.offset + i);
   2.554 +    };
   2.555 +
   2.556 +    // =======================================================
   2.557 +
   2.558 +    JSIO.ArrayReader         = _arrayReader;
   2.559 +    JSIO.StreamReader        = _streamSegmentReader;
   2.560 +    JSIO.StreamSegmentReader = _streamSegmentReader;
   2.561 +
   2.562 +})();
   2.563 +
   2.564 +
   2.565 +/// JSIO.BasicByteReaders.js ends
   2.566 +
   2.567 +// JSIO.BinaryUrlStream.js
   2.568 +// ------------------------------------------------------------------
   2.569 +//
   2.570 +// a class that acts as a stream wrapper around binary files obtained from URLs.
   2.571 +//
   2.572 +// =======================================================
   2.573 +//
   2.574 +// Copyleft (c) 2008, Andy G.P. Na <nagoon97@naver.com> via an MIT-style license
   2.575 +// Copyleft (c) 2012, Brendan Byrd via GPL
   2.576 +//
   2.577 +// This work is licensed under the GPLv3.
   2.578 +
   2.579 +(function(){
   2.580 +    var version  = "2.0 2012Feb";
   2.581 +    var typename = "JSIO.BinaryUrlStream";
   2.582 +
   2.583 +    if ((typeof JSIO !== "object") ||
   2.584 +        (typeof JSIO.version !== "string") ||
   2.585 +        (JSIO.version.length < 3) ||
   2.586 +        (JSIO.version.substring(0,3) !== "2.0"))
   2.587 +            JSIO.throwError('This extension requires JSIO.core.js v2.0', typename);
   2.588 +
   2.589 +    if (typeof JSIO._ByteReaderBase !== "function")
   2.590 +        JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename);
   2.591 +
   2.592 +    if (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) {
   2.593 +        var IEBinaryToArray_ByteStr_Script =
   2.594 +            "<!-- IEBinaryToArray_ByteStr -->\r\n"+
   2.595 +            "<script type='text/vbscript'>\r\n"+
   2.596 +            "Function IEBinaryToArray_ByteStr(Binary)\r\n"+
   2.597 +            "   IEBinaryToArray_ByteStr = CStr(Binary)\r\n"+
   2.598 +            "End Function\r\n"+
   2.599 +            "Function IEBinaryToArray_ByteAsc_Last(Binary)\r\n"+
   2.600 +            "   Dim lastIndex\r\n"+
   2.601 +            "   lastIndex = LenB(Binary)\r\n"+
   2.602 +            "   if lastIndex mod 2 Then\r\n"+
   2.603 +            "      IEBinaryToArray_ByteAsc_Last = AscB( MidB( Binary, lastIndex, 1 ) )\r\n"+
   2.604 +            "   Else\r\n"+
   2.605 +            "      IEBinaryToArray_ByteAsc_Last = -1\r\n"+
   2.606 +            "   End If\r\n"+
   2.607 +            "End Function\r\n"+
   2.608 +            "</script>\r\n";
   2.609 +
   2.610 +        // inject VBScript
   2.611 +        document.write(IEBinaryToArray_ByteStr_Script);
   2.612 +    }
   2.613 +
   2.614 +    JSIO.IEByteMapping = null;
   2.615 +
   2.616 +    var bus = function(url, callback) {
   2.617 +        if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.BinaryUrlStream', 'ctor');
   2.618 +
   2.619 +        this.callback   = callback;
   2.620 +        this.position   = 0;
   2.621 +        this.length     = null;
   2.622 +        this.readByte   = JSIO.ArrayReader.prototype.readByte;
   2.623 +        this.readByteAt = JSIO.ArrayReader.prototype.readByteAt;
   2.624 +        this.req        = null;
   2.625 +        this._typename  = typename;
   2.626 +        this._version   = version;
   2.627 +        this.status     = "-none-";
   2.628 +
   2.629 +        var _IeGetBinResource = function(fileURL){
   2.630 +            var binStream = this;
   2.631 +            // http://stackoverflow.com/questions/1919972/how-do-i-access-xhr-responsebody-for-binary-data-from-javascript-in-ie
   2.632 +
   2.633 +            // my helper to convert from responseBody to a byte array
   2.634 +            var convertResponseBodyToArray = function (binary) {
   2.635 +                var byteArray = new Array;
   2.636 +
   2.637 +                try {
   2.638 +                    // very fast; very little work involved
   2.639 +                    byteArray = new VBArray(binary).toArray();
   2.640 +                }
   2.641 +                catch(err) {
   2.642 +                    // use the BinaryToArray VBScript
   2.643 +                    if (!JSIO.IEByteMapping) {
   2.644 +                        JSIO.IEByteMapping = {};
   2.645 +                        for ( var i = 0; i < 256; i++ ) {
   2.646 +                            for ( var j = 0; j < 256; j++ ) {
   2.647 +                                JSIO.IEByteMapping[ String.fromCharCode( i + j * 256 ) ] = [ i, j ];
   2.648 +                            }
   2.649 +                        }
   2.650 +                    }
   2.651 +                    var rawBytes = IEBinaryToArray_ByteStr(binary);
   2.652 +                    var lastAsc  = IEBinaryToArray_ByteAsc_Last(binary);
   2.653 +
   2.654 +                    for ( var i = 0; i < rawBytes.length; i++ ) {
   2.655 +                        byteArray.push.apply(byteArray, JSIO.IEByteMapping[ rawBytes.substr(i,1) ]);
   2.656 +                    }
   2.657 +                    if (lastAsc >= 0) byteArray.push(lastAsc);
   2.658 +                }
   2.659 +
   2.660 +                return byteArray;
   2.661 +            };
   2.662 +
   2.663 +            this.req = (function() {
   2.664 +                if      (window.XMLHttpRequest) return new window.XMLHttpRequest();
   2.665 +                else if (window.ActiveXObject) {
   2.666 +                    // the many versions of IE's XML fetchers
   2.667 +                    var AXOs = [
   2.668 +                        'MSXML2.XMLHTTP.6.0',
   2.669 +                        'MSXML2.XMLHTTP.5.0',
   2.670 +                        'MSXML2.XMLHTTP.4.0',
   2.671 +                        'MSXML2.XMLHTTP.3.0',
   2.672 +                        'MSXML2.XMLHTTP',
   2.673 +                        'Microsoft.XMLHTTP',
   2.674 +                        'MSXML.XMLHTTP'
   2.675 +                    ];
   2.676 +                    for (var i = 0; i < AXOs.length; i++) {
   2.677 +                        try      { return new ActiveXObject(AXOs[i]); }
   2.678 +                        catch(e) { continue; }
   2.679 +                    }
   2.680 +                }
   2.681 +                return null;
   2.682 +            })();
   2.683 +            this.req.open("GET", fileURL, true);
   2.684 +            this.req.setRequestHeader("Accept-Charset", "x-user-defined");
   2.685 +            this.req.onreadystatechange = function(event){
   2.686 +                if (binStream.req.readyState == 4) {
   2.687 +                    binStream.status = "Status: " + binStream.req.status + ' ' + binStream.req.statusText;
   2.688 +                    if (binStream.req.status == 200) {
   2.689 +                        binStream.array  = convertResponseBodyToArray(binStream.req.responseBody);
   2.690 +                        binStream.length = binStream.array.length;
   2.691 +                        if (binStream.length < 0) this._throwError('Failed to load "'+ fileURL + '" after converting');
   2.692 +
   2.693 +                        if (typeof binStream.callback == "function") binStream.callback(binStream);
   2.694 +                    }
   2.695 +                    else {
   2.696 +                        binStream._throwError('Failed to load "'+ fileURL + '": HTTP ' + binStream.status);
   2.697 +                    }
   2.698 +                }
   2.699 +            };
   2.700 +            this.req.send();
   2.701 +        };
   2.702 +
   2.703 +        var _NormalGetBinResource = function(fileURL){
   2.704 +            var binStream= this;
   2.705 +            this.req = new XMLHttpRequest();
   2.706 +            this.req.open('GET', fileURL, true);
   2.707 +            this.req.onreadystatechange = function(aEvt) {
   2.708 +                if (binStream.req.readyState == 4) {
   2.709 +                    binStream.status = "Status: " + binStream.req.status + ' ' + binStream.req.statusText;
   2.710 +                    if(binStream.req.status == 200){
   2.711 +                        var fileContents = binStream.req.responseText;
   2.712 +                        binStream.length = fileContents.byteLength;
   2.713 +                        binStream.array  = fileContents.split('');
   2.714 +                        for ( var i = 0; i < binStream.array.length; i++ ) {
   2.715 +                            binStream.array[i] = binStream.array[i].charCodeAt(0) & 0xff;
   2.716 +                        }
   2.717 +
   2.718 +                        if (typeof binStream.callback == "function") binStream.callback(binStream);
   2.719 +                    }
   2.720 +                    else {
   2.721 +                        binStream._throwError('Failed to load "'+ fileURL + '": HTTP ' + binStream.status);
   2.722 +                    }
   2.723 +                }
   2.724 +            };
   2.725 +            //XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com]
   2.726 +            this.req.overrideMimeType('text/plain; charset=x-user-defined');
   2.727 +            this.req.send(null);
   2.728 +        };
   2.729 +
   2.730 +        // http://stackoverflow.com/questions/327685/is-there-a-way-to-read-binary-data-into-javascript
   2.731 +        var _ArrayBufferGetBinResource = function(fileURL){
   2.732 +            var binStream= this;
   2.733 +            this.req = new XMLHttpRequest();
   2.734 +            this.req.open('GET', fileURL, true);
   2.735 +            this.req.onreadystatechange = function(aEvt) {
   2.736 +                if (binStream.req.readyState == 4) {
   2.737 +                    binStream.status = "Status: " + binStream.req.status + ' ' + binStream.req.statusText;
   2.738 +                    if(binStream.req.status == 200){
   2.739 +                        var fileContents = binStream.req.response;
   2.740 +                        binStream.length = fileContents.byteLength;
   2.741 +                        binStream.array = new Uint8Array(fileContents);
   2.742 +                        if (typeof binStream.callback == "function") binStream.callback(binStream);
   2.743 +                    }
   2.744 +                    else {
   2.745 +                        binStream._throwError('Failed to load "'+ fileURL + '": HTTP ' + binStream.status);
   2.746 +                    }
   2.747 +                }
   2.748 +            };
   2.749 +            this.req.responseType = 'arraybuffer';
   2.750 +            // http://stackoverflow.com/questions/11284728/how-do-i-access-8-bit-binary-data-from-javascript-in-opera
   2.751 +            this.req.overrideMimeType('application/octet-stream; charset=x-user-defined');
   2.752 +            this.req.send(null);
   2.753 +        };
   2.754 +
   2.755 +
   2.756 +        if      (typeof ArrayBuffer !== 'undefined')                                       _ArrayBufferGetBinResource.apply(this, [url]);
   2.757 +        else if (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) _IeGetBinResource.apply(this, [url]);
   2.758 +        else                                                                               _NormalGetBinResource.apply(this, [url]);
   2.759 +    };
   2.760 +
   2.761 +    bus.prototype = new JSIO._ByteReaderBase();
   2.762 +
   2.763 +    bus.prototype.readByte = function(){
   2.764 +        var byte = this.readByteAt(this.position++);
   2.765 +        return (byte === null || byte === undefined) ? null : byte;
   2.766 +    };
   2.767 +
   2.768 +    JSIO.BinaryUrlStream = bus;
   2.769 +
   2.770 +})();
   2.771 +
   2.772 +/// JSIO.BinaryUrlStream.js ends
   2.773 +
   2.774 +// JSIO.TextDecoder.js
   2.775 +// ------------------------------------------------------------------
   2.776 +//
   2.777 +// Part of the JSIO library.  Adds text decoders, for UTF-8 and UTF-16,
   2.778 +// and plain text.
   2.779 +//
   2.780 +// =======================================================
   2.781 +//
   2.782 +// Derived in part from work by notmasteryet.
   2.783 +//   http://www.codeproject.com/KB/scripting/Javascript_binaryenc.aspx
   2.784 +
   2.785 +// Copyleft (c) 2008, notmasteryet via an MIT-style license
   2.786 +// Copyleft (c) 2010, Dino Chiesa via MS-PL
   2.787 +// Copyleft (c) 2012, Brendan Byrd via GPL
   2.788 +//
   2.789 +// This work is licensed under the GPLv3.
   2.790 +
   2.791 +(function(){
   2.792 +    var version = "2.0 2012Feb";
   2.793 +    var typename = "JSIO.TextDecoder";
   2.794 +
   2.795 +    if ((typeof JSIO !== "object") ||
   2.796 +        (typeof JSIO.version !== "string") ||
   2.797 +        (JSIO.version.length < 3) ||
   2.798 +        (JSIO.version.substring(0,3) !== "2.0"))
   2.799 +            JSIO.throwError('This extension requires JSIO.core.js v2.0', typename);
   2.800 +
   2.801 +    if (typeof JSIO._ByteReaderBase !== "function")
   2.802 +        JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename);
   2.803 +
   2.804 +    var _ansi = function(reader) {
   2.805 +        if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.TextDecoder.ANSI', 'ctor');
   2.806 +        this.byteReader = reader;
   2.807 +        this.charWidth = 1;
   2.808 +        this._version = version;
   2.809 +        this._typename = typename + ".ANSI";
   2.810 +        return this;
   2.811 +    };
   2.812 +
   2.813 +    _ansi.prototype.readChar = function() {
   2.814 +        var code = this.byteReader.readByte();
   2.815 +        return (code < 0) ? null : String.fromCharCode(code);
   2.816 +    };
   2.817 +
   2.818 +    _ansi.prototype.parseChar = function(code) {
   2.819 +        return (code < 0) ? null : String.fromCharCode(code);
   2.820 +    };
   2.821 +
   2.822 +    var _utf16 = function(reader) {
   2.823 +        if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.TextDecoder.UTF16', 'ctor');
   2.824 +        this.byteReader = reader;
   2.825 +        this.charWidth = 2;
   2.826 +        this.bomState = 0;
   2.827 +        this._version = version;
   2.828 +        this._typename = typename + ".UTF16";
   2.829 +        return this;
   2.830 +    };
   2.831 +
   2.832 +    _utf16.prototype.readChar = function() {
   2.833 +        var b1 = this.byteReader.readByte();
   2.834 +        if (b1 < 0) return null;
   2.835 +        var b2 = this.byteReader.readByte();
   2.836 +        if (b2 < 0) this._throwError('Incomplete UTF16 character', null, 'readChar');
   2.837 +
   2.838 +        if ((this.bomState === 0) && ((b1 + b2) == 509)) {
   2.839 +            this.bomState = (b2 == 254) ? 1 : 2;
   2.840 +
   2.841 +            b1 = this.byteReader.readByte();
   2.842 +            if (b1 < 0) return null;
   2.843 +            b2 = this.byteReader.readByte();
   2.844 +            if (b2 < 0) this._throwError('Incomplete UTF16 character', null, 'readChar');
   2.845 +        }
   2.846 +        else {
   2.847 +            this.bomState = 1;
   2.848 +        }
   2.849 +        return this.parseChar(b1, b2);
   2.850 +    };
   2.851 +
   2.852 +    _utf16.prototype.parseChar = function(b1, b2) {
   2.853 +        return String.fromCharCode( this.bomState == 1 ? (b2 << 8 | b1) : (b1 << 8 | b2) );
   2.854 +    }
   2.855 +
   2.856 +    /* RFC 3629 */
   2.857 +    var _utf8 = function(reader) {
   2.858 +        if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.TextDecoder.UTF8', 'ctor');
   2.859 +        this.byteReader = reader;
   2.860 +        this.charWidth = null;
   2.861 +        this.waitBom = true;
   2.862 +        this.strict = false;
   2.863 +        this.pendingChar = null;
   2.864 +        this._version = version;
   2.865 +        this._typename = typename + ".UTF8";
   2.866 +        return this;
   2.867 +    };
   2.868 +
   2.869 +    _utf8.prototype.readChar = function() {
   2.870 +        var ch = null;
   2.871 +        do {
   2.872 +            if (this.pendingChar !== null) {
   2.873 +                ch = this.pendingChar;
   2.874 +                this.pendingChar = null;
   2.875 +            }
   2.876 +            else {
   2.877 +                var b1 = this.byteReader.readByte();
   2.878 +                if (b1 === null) return null;
   2.879 +
   2.880 +                if ((b1 & 0x80) === 0) ch = String.fromCharCode(b1);
   2.881 +                else {
   2.882 +                    var currentPrefix = 0xC0;
   2.883 +                    var ttlBytes = 0;
   2.884 +                    do {
   2.885 +                        var mask = currentPrefix >> 1 | 0x80;
   2.886 +                        if((b1 & mask) == currentPrefix) break;
   2.887 +                        currentPrefix = currentPrefix >> 1 | 0x80;
   2.888 +                    } while(++ttlBytes < 5);
   2.889 +
   2.890 +                    if (ttlBytes > 0) {
   2.891 +                        var code;
   2.892 +                        if (ttlBytes === 1) code = (b1 & 0x1F) << 6 | (this.byteReader.readByte() & 0x3F);
   2.893 +                        else {
   2.894 +                            code = code << 6*ttlBytes
   2.895 +                            var bytes = this.byteReader.read(ttlBytes);
   2.896 +                            for (var i = 0; i > ttlBytes; i++) {
   2.897 +                                var bi = bytes[i];
   2.898 +                                if ((bi & 0xC0) != 0x80) this._throwError('Invalid sequence character', null, 'readChar');
   2.899 +                                code = (code << 6) | (bi & 0x3F);
   2.900 +                            }
   2.901 +                        }
   2.902 +
   2.903 +                        if (code <= 0xFFFF) {
   2.904 +                            ch = (code == 0xFEFF && this.waitBom) ? null : String.fromCharCode(code);
   2.905 +                        }
   2.906 +                        else {
   2.907 +                            var v = code - 0x10000;
   2.908 +                            var w1 = 0xD800 | ((v >> 10) & 0x3FF);
   2.909 +                            var w2 = 0xDC00 | (v & 0x3FF);
   2.910 +                            this.pendingChar = String.fromCharCode(w2);
   2.911 +                            ch = String.fromCharCode(w1);
   2.912 +                        }
   2.913 +                    }
   2.914 +                    else {
   2.915 +                        // a byte higher than 0x80.
   2.916 +                        if (this.strict) this._throwError('Invalid character', null, 'readChar');
   2.917 +                        // fall back to "super ascii" (eg IBM-437)
   2.918 +                        else ch = String.fromCharCode(b1);
   2.919 +                    }
   2.920 +                }
   2.921 +            }
   2.922 +            this.waitBom = false;
   2.923 +        } while(ch === null);
   2.924 +        return ch;
   2.925 +    };
   2.926 +
   2.927 +    JSIO.TextDecoder = {
   2.928 +        Default : _ansi,
   2.929 +        ANSI    : _ansi,
   2.930 +        UTF16   : _utf16,
   2.931 +        UTF8    : _utf8
   2.932 +    };
   2.933 +
   2.934 +})();
   2.935 +
   2.936 +
   2.937 +/// JSIO.TextDecoder.js ends
   2.938 +
   2.939 +// JSIO.TextReader.js
   2.940 +// ------------------------------------------------------------------
   2.941 +//
   2.942 +// A reader class that decodes text as it reads.
   2.943 +//
   2.944 +// =======================================================
   2.945 +//
   2.946 +// Methods:
   2.947 +//    readChar()         = read 1 char
   2.948 +//    read(n)            = read n chars
   2.949 +//    readLine()         = read one line of data (to \n)
   2.950 +//    unreadChar(ch)     = unread one char
   2.951 +//    readToEnd()        = read all data in the reader;
   2.952 +//                         return a string.
   2.953 +//    beginReadToEnd(cb) = asynchronously read all data.
   2.954 +//
   2.955 +// =======================================================
   2.956 +//
   2.957 +// Derived in part from work by notmasteryet.
   2.958 +//   http://www.codeproject.com/KB/scripting/Javascript_binaryenc.aspx
   2.959 +//
   2.960 +// Copyleft (c) 2008, notmasteryet via an MIT-style license
   2.961 +// Copyleft (c) 2010, Dino Chiesa via MS-PL
   2.962 +// Copyleft (c) 2012, Brendan Byrd via GPL
   2.963 +//
   2.964 +// This work is licensed under the GPLv3.
   2.965 +
   2.966 +
   2.967 +(function(){
   2.968 +    var version = "2.0 2012Feb";
   2.969 +    var typename = "JSIO.TextReader";
   2.970 +
   2.971 +    if (typeof JSIO._ByteReaderBase !== "function")
   2.972 +        JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename);
   2.973 +
   2.974 +    var tr =  function(textDecoder) {
   2.975 +        if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename, 'ctor');
   2.976 +        this.decoder   = textDecoder;
   2.977 +        this._version  = version;
   2.978 +        this._typename = typename;
   2.979 +        this.unreads   = [];
   2.980 +    };
   2.981 +
   2.982 +    // read one char
   2.983 +    tr.prototype.readChar = function() {
   2.984 +        return (this.unreads.length > 0) ? this.unreads.pop() : this.decoder.readChar();
   2.985 +    };
   2.986 +
   2.987 +    // read a length of data
   2.988 +    tr.prototype.read = function(n) {
   2.989 +        // ANSI makes this easy
   2.990 +        if (this.decoder.charWidth === 1) return JSIO.massApply(String.fromCharCode, new String, this.decoder.byteReader.read(n), true);
   2.991 +
   2.992 +        var s = "";
   2.993 +        for (vari=0; i<n; i++) {
   2.994 +            var ch = this.readChar();
   2.995 +            if (ch !== null) s+= ch;
   2.996 +            else             break;
   2.997 +        }
   2.998 +        return s;
   2.999 +    };
  2.1000 +
  2.1001 +    tr.prototype.unreadChar = function(ch) {
  2.1002 +        this.unreads.push(ch);
  2.1003 +    };
  2.1004 +
  2.1005 +    tr.prototype.readToEnd = function() {
  2.1006 +        // ANSI makes this easy
  2.1007 +        if (this.decoder.charWidth === 1) return JSIO.massApply(String.fromCharCode, new String, this.decoder.byteReader.readToEnd(n), true);
  2.1008 +
  2.1009 +        var slarge = "";
  2.1010 +        var s = "";
  2.1011 +        var c = 0;
  2.1012 +        var ch = this.readChar();
  2.1013 +        while(ch !== null) {
  2.1014 +            s += ch;
  2.1015 +            c++;
  2.1016 +            if(c >= 32768) {
  2.1017 +                slarge += s;
  2.1018 +                s = "";
  2.1019 +                c = 0;
  2.1020 +            }
  2.1021 +            ch = this.readChar();
  2.1022 +        }
  2.1023 +        return slarge + s;
  2.1024 +    };
  2.1025 +
  2.1026 +    tr.prototype.beginReadToEnd = function(callback) {
  2.1027 +        // ANSI makes this easy
  2.1028 +        if (this.decoder.charWidth === 1) {
  2.1029 +            this.decoder.byteReader.beginReadToEnd(function (bytes) {
  2.1030 +                callback( JSIO.massApply(String.fromCharCode, new String, bytes, true) );
  2.1031 +            });
  2.1032 +            return null;
  2.1033 +        }
  2.1034 +
  2.1035 +        var slarge = "";
  2.1036 +        var s = "";
  2.1037 +        var txtrdr = this;
  2.1038 +
  2.1039 +        var readBatchAsync = function() {
  2.1040 +            var c = 0;
  2.1041 +            var ch = txtrdr.readChar();
  2.1042 +            while(ch !== null) {
  2.1043 +                s += ch;c++;
  2.1044 +                if(c >= 32768) {
  2.1045 +                    slarge += s;
  2.1046 +                    s = "";
  2.1047 +                    break;
  2.1048 +                }
  2.1049 +                ch = txtrdr.readChar();
  2.1050 +            }
  2.1051 +            if (ch!==null){
  2.1052 +                setTimeout(readBatchAsync, 1);
  2.1053 +            }
  2.1054 +            else {
  2.1055 +                callback(slarge+s);
  2.1056 +            }
  2.1057 +        };
  2.1058 +
  2.1059 +        // kickoff
  2.1060 +        setTimeout(readBatchAsync, 1);  // always async, in ALL situations
  2.1061 +        return null;
  2.1062 +    };
  2.1063 +
  2.1064 +    tr.prototype.readLine = function() {
  2.1065 +        var s = "";
  2.1066 +        var ch = this.readChar();
  2.1067 +        if (ch === null) return null;
  2.1068 +
  2.1069 +        while(ch != "\r" && ch != "\n") {
  2.1070 +            s += ch;
  2.1071 +            ch = this.readChar();
  2.1072 +            if (ch === null) return s;
  2.1073 +        }
  2.1074 +        if(ch == "\r") {
  2.1075 +            ch = this.readChar();
  2.1076 +            if(ch !== null && ch != "\n"){
  2.1077 +                this.unreadChar(ch);
  2.1078 +            }
  2.1079 +        }
  2.1080 +        return s;
  2.1081 +    };
  2.1082 +
  2.1083 +    JSIO.TextReader = tr;
  2.1084 +
  2.1085 +})();
  2.1086 +
  2.1087 +
  2.1088 +/// JSIO.TextReader.js ends
  2.1089 +
  2.1090 +// JSIO.Crc32.js
  2.1091 +//
  2.1092 +// Part of the JSIO library.  This adds an CRC32-calculating
  2.1093 +// ByteReader to JSIO.
  2.1094 +//
  2.1095 +// =======================================================
  2.1096 +//
  2.1097 +// A ByteReader exposes an interface with these functions:
  2.1098 +//
  2.1099 +//    readByte()
  2.1100 +//       must return null when EOF is reached.
  2.1101 +//
  2.1102 +//    readToEnd()
  2.1103 +//       returns an array of all bytes read, to EOF
  2.1104 +//
  2.1105 +//    beginReadToEnd(callback)
  2.1106 +//       async version of the above
  2.1107 +//
  2.1108 +//    readBytes(n)
  2.1109 +//       returns an array of all n bytes read from the source
  2.1110 +//
  2.1111 +//    beginReadBytes(n, callback)
  2.1112 +//       async version of the above
  2.1113 +//
  2.1114 +// =======================================================
  2.1115 +//
  2.1116 +// Copyleft (c) 2010, Dino Chiesa via MS-PL
  2.1117 +// Copyleft (c) 2012, Brendan Byrd via GPL
  2.1118 +//
  2.1119 +// This work is licensed under the GPLv3.
  2.1120 +
  2.1121 +(function(){
  2.1122 +    var version = "2.0 2012Feb";
  2.1123 +    var typename = "JSIO.Crc32";
  2.1124 +
  2.1125 +    if (typeof JSIO._ByteReaderBase !== "function")
  2.1126 +        JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename);
  2.1127 +
  2.1128 +    JSIO.crc32Table = null;
  2.1129 +    JSIO.crc32Polynomial = 0xEDB88320;
  2.1130 +
  2.1131 +    var crc32TableCalc = function () {
  2.1132 +        // do this once only, for all instances
  2.1133 +        if (JSIO.crc32Table) return;
  2.1134 +        JSIO.crc32Table = new Array(256);
  2.1135 +        for (var i = 0; i < 256; i++) {
  2.1136 +            var c=i;
  2.1137 +            for (var k = 0; k < 8; k++) {
  2.1138 +                if ((c & 1) == 1) c = JSIO.crc32Polynomial ^ (c>>>1);
  2.1139 +                else              c >>>= 1;
  2.1140 +            }
  2.1141 +            JSIO.crc32Table[i] = c;
  2.1142 +        }
  2.1143 +    };
  2.1144 +
  2.1145 +    JSIO.computeCrc32 = function(str) {
  2.1146 +        crc32TableCalc(); // once
  2.1147 +        var c = 0xFFFFFFFF;
  2.1148 +        var sL = str.length;
  2.1149 +        if (typeof str == "object") {
  2.1150 +            for (var n1=0; n1<sL; n1++) {
  2.1151 +                c = JSIO.crc32Table[(c&0xff) ^ str[n1]] ^ (c>>>8);
  2.1152 +            }
  2.1153 +        } else {
  2.1154 +            for (var n2=0; n2<sL; n2++) {
  2.1155 +                c = JSIO.crc32Table[(c&0xff) ^ str.charCodeAt(n2)] ^ (c>>>8);
  2.1156 +            }
  2.1157 +        }
  2.1158 +        c ^= 0xFFFFFFFF;
  2.1159 +        if (c < 0) c += 0xFFFFFFFF+1;
  2.1160 +        return c;
  2.1161 +    };
  2.1162 +
  2.1163 +    // =======================================================
  2.1164 +    var _crc32 = function() {
  2.1165 +        if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename, 'ctor');
  2.1166 +        crc32TableCalc(); // once
  2.1167 +        this._typename = typename;
  2.1168 +        this._version  = version;
  2.1169 +        this._runningCrc32 = 0xFFFFFFFF;
  2.1170 +    };
  2.1171 +
  2.1172 +    _crc32.prototype.slurpByte = function(b) {
  2.1173 +        var r = this._runningCrc32;
  2.1174 +        this._runningCrc32 = r>>>8 ^ JSIO.crc32Table[b ^ (r & 0x000000FF)];
  2.1175 +    };
  2.1176 +
  2.1177 +    _crc32.prototype.result = function() {
  2.1178 +        var c = this._runningCrc32 ^ 0xFFFFFFFF;
  2.1179 +        if (c < 0) c += 0xFFFFFFFF+1;
  2.1180 +        return c;
  2.1181 +    };
  2.1182 +    // =======================================================
  2.1183 +
  2.1184 +
  2.1185 +
  2.1186 +    var _crc32CalculatingReader = function(reader) {
  2.1187 +        if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.Crc32Reader', 'ctor');
  2.1188 +        this._byteReader = reader;
  2.1189 +        this._typename = "JSIO.Crc32Reader";
  2.1190 +        this._version = version;
  2.1191 +        this._crc32 = new JSIO.Crc32();
  2.1192 +    };
  2.1193 +
  2.1194 +    _crc32CalculatingReader.prototype = new JSIO._ByteReaderBase();
  2.1195 +
  2.1196 +    _crc32CalculatingReader.prototype.readByte = function() {
  2.1197 +        var b = this._byteReader.readByte();
  2.1198 +        if (b !== null) this._crc32.slurpByte(b);
  2.1199 +        this.position++;
  2.1200 +        return b;
  2.1201 +    };
  2.1202 +
  2.1203 +    _crc32CalculatingReader.prototype.read = function(len) {
  2.1204 +        if (len === 0) return [];
  2.1205 +        var bytes = this._byteReader.read(len);
  2.1206 +        len = bytes.length;
  2.1207 +
  2.1208 +        var tbl = JSIO.crc32Table;
  2.1209 +        var r = this._crc32._runningCrc32;
  2.1210 +        var t;
  2.1211 +        for (var i = 0; i < len; i++) {
  2.1212 +            t = tbl[ bytes[i] ^ (r & 0x000000FF) ];
  2.1213 +            r = (r >>> 8) ^ t;
  2.1214 +        }
  2.1215 +        this._crc32._runningCrc32 = r;
  2.1216 +
  2.1217 +        this.position += len;
  2.1218 +        return bytes;
  2.1219 +    }
  2.1220 +
  2.1221 +    _crc32CalculatingReader.prototype.crc32 = function() {
  2.1222 +        return this._crc32.result();
  2.1223 +    };
  2.1224 +
  2.1225 +    JSIO.Crc32 = _crc32;
  2.1226 +    JSIO.Crc32Reader = _crc32CalculatingReader;
  2.1227 +
  2.1228 +})();
  2.1229 +
  2.1230 +/// JSIO.CRC32.js ends
  2.1231 +// JSIO.InflatingReader.js
  2.1232 +// ------------------------------------------------------------------
  2.1233 +//
  2.1234 +// Part of the JSIO library.  This adds an Inflating ByteReader to
  2.1235 +// JSIO.
  2.1236 +//
  2.1237 +// =======================================================
  2.1238 +//
  2.1239 +// A ByteReader exposes an interface with these functions:
  2.1240 +//
  2.1241 +//    readByte()
  2.1242 +//       must return null when EOF is reached.
  2.1243 +//
  2.1244 +//    readToEnd()
  2.1245 +//       returns an array of all bytes read, to EOF
  2.1246 +//
  2.1247 +//    beginReadToEnd(callback)
  2.1248 +//       async version of the above
  2.1249 +//
  2.1250 +//    readBytes(n)
  2.1251 +//       returns an array of all n bytes read from the source
  2.1252 +//
  2.1253 +//    beginReadBytes(n, callback)
  2.1254 +//       async version of the above
  2.1255 +//
  2.1256 +// =======================================================
  2.1257 +//
  2.1258 +// Derived in part from work by notmasteryet.
  2.1259 +//   http://www.codeproject.com/KB/scripting/Javascript_binaryenc.aspx
  2.1260 +//
  2.1261 +// Copyleft (c) 2008, notmasteryet via an MIT-style license
  2.1262 +// Copyleft (c) 2010, Dino Chiesa via MS-PL
  2.1263 +// Copyleft (c) 2012, Brendan Byrd via GPL
  2.1264 +//
  2.1265 +// This work is licensed under the GPLv3.
  2.1266 +
  2.1267 +
  2.1268 +(function(){
  2.1269 +    var version = "2.0 2012Feb";
  2.1270 +    var typename = "JSIO.InflatingReader";
  2.1271 +
  2.1272 +    if (typeof JSIO._ByteReaderBase !== "function")
  2.1273 +        JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename);
  2.1274 +
  2.1275 +    // =======================================================
  2.1276 +    //  _InternalBitReader is used internally in the InflatingReader class.
  2.1277 +    //
  2.1278 +
  2.1279 +    JSIO.bitShiftTable = null;
  2.1280 +
  2.1281 +    var bitShiftTableCalc = function () {
  2.1282 +        // do this once only, for all instances
  2.1283 +        if (JSIO.bitShiftTable) return;
  2.1284 +
  2.1285 +        var bits = 8;
  2.1286 +        JSIO.bitShiftTable = {
  2.1287 +            LSB: new Array(bits),
  2.1288 +            MSB: new Array(bits)
  2.1289 +        }
  2.1290 +        for (var bp = 0; bp < bits; bp++) {
  2.1291 +            var lim = bits - bp;
  2.1292 +            JSIO.bitShiftTable.LSB[bp] = new Array(lim);
  2.1293 +            JSIO.bitShiftTable.MSB[bp] = new Array(lim);
  2.1294 +
  2.1295 +            var maskLSB = 1 << bp;
  2.1296 +            var maskMSB = 1 << lim-1;
  2.1297 +            for (var len = 1; len <= lim; len++) {
  2.1298 +                JSIO.bitShiftTable.LSB[bp][len-1] = maskLSB;
  2.1299 +                JSIO.bitShiftTable.MSB[bp][len-1] = maskMSB;
  2.1300 +                maskLSB |= 1 << bp+len;
  2.1301 +                maskMSB |= 1 << lim-len-1;
  2.1302 +            }
  2.1303 +        }
  2.1304 +    };
  2.1305 +
  2.1306 +    var _InternalBitReader = function(reader) {
  2.1307 +        if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename + '._InternalBitReader.ctor');
  2.1308 +        this.bitsLength = 0;
  2.1309 +        this.bits = 0;
  2.1310 +        this.byteReader = reader;
  2.1311 +        this._typeName = typename + "._InternalBitReader";
  2.1312 +        this._version = version;
  2.1313 +        bitShiftTableCalc();
  2.1314 +    };
  2.1315 +
  2.1316 +    _InternalBitReader.prototype._throwError = JSIO.throwError;
  2.1317 +
  2.1318 +    _InternalBitReader.prototype.readBit = function() {
  2.1319 +        if (this.bitsLength === 0) {
  2.1320 +            var nextByte = this.byteReader.readByte();
  2.1321 +            if (nextByte === null) this._throwError('Unexpected end of stream', null, 'readBit');
  2.1322 +            this.bits = nextByte;
  2.1323 +            this.bitsLength = 8;
  2.1324 +        }
  2.1325 +
  2.1326 +        var bit = (this.bits & 1 << 8-this.bitsLength) !== 0;
  2.1327 +        this.bitsLength--;
  2.1328 +        return bit;
  2.1329 +    };
  2.1330 +
  2.1331 +    _InternalBitReader.prototype.align = function() { this.bitsLength = 0; };
  2.1332 +
  2.1333 +    _InternalBitReader.prototype.readBits = function(len, type) {
  2.1334 +        var data = 0;
  2.1335 +        type = type || 'LSB';
  2.1336 +        var tbl = JSIO.bitShiftTable[type];
  2.1337 +        var dl = 0;
  2.1338 +        while (len > 0) {
  2.1339 +            if (this.bitsLength === 0) {
  2.1340 +                var nextByte = this.byteReader.readByte();
  2.1341 +                if (nextByte === null) this._throwError('Unexpected end of stream', null, 'read'+type);
  2.1342 +                this.bits = nextByte;
  2.1343 +                this.bitsLength = 8;
  2.1344 +            }
  2.1345 +
  2.1346 +            var maskLen = (this.bitsLength >= len) ? len : this.bitsLength;
  2.1347 +            var bitsPos = 8-this.bitsLength;
  2.1348 +            data |= (this.bits & tbl[bitsPos][maskLen-1]) >>> bitsPos << dl;
  2.1349 +
  2.1350 +            dl += maskLen;
  2.1351 +            len -= maskLen;
  2.1352 +            this.bitsLength -= maskLen;
  2.1353 +        };
  2.1354 +        return data;
  2.1355 +    };
  2.1356 +
  2.1357 +    _InternalBitReader.prototype.readLSB = function(len) { return this.readBits(len, 'LSB'); }
  2.1358 +    _InternalBitReader.prototype.readMSB = function(len) { return this.readBits(len, 'MSB'); }
  2.1359 +
  2.1360 +    //
  2.1361 +    // =======================================================
  2.1362 +
  2.1363 +
  2.1364 +    /* inflating ByteReader - RFC 1951 */
  2.1365 +    var _inflatingReader = function(reader) {
  2.1366 +        if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename, 'ctor');
  2.1367 +        this._byteReader = reader;
  2.1368 +        this._bitReader = new _InternalBitReader(reader);
  2.1369 +        this._buffer = [];
  2.1370 +        this._bufferPosition = 0;
  2.1371 +        this._state = 0;
  2.1372 +        this._blockFinal = false;
  2.1373 +        this._typeName = typename;
  2.1374 +        this._version = version;
  2.1375 +        return this;
  2.1376 +    };
  2.1377 +
  2.1378 +
  2.1379 +    // shared fns and variables
  2.1380 +
  2.1381 +    var staticCodes = null;
  2.1382 +    var staticDistances = null;
  2.1383 +
  2.1384 +    var clenMap = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
  2.1385 +
  2.1386 +    var buildCodes = function(lengths){
  2.1387 +        var i=0;
  2.1388 +        var codes = new Array(lengths.length);
  2.1389 +        var maxBits = lengths[0];
  2.1390 +        for (i=1; i<lengths.length; i++) {
  2.1391 +            if (maxBits < lengths[i]) maxBits = lengths[i];
  2.1392 +        }
  2.1393 +
  2.1394 +        var bitLengthsCount = new Array(maxBits + 1);
  2.1395 +        for (i=0; i<=maxBits; i++) bitLengthsCount[i]=0;
  2.1396 +
  2.1397 +        for (i=0; i<lengths.length; i++) {
  2.1398 +            ++bitLengthsCount[lengths[i]];
  2.1399 +        }
  2.1400 +
  2.1401 +        var nextCode = new Array(maxBits + 1);
  2.1402 +        var code = 0;
  2.1403 +        bitLengthsCount[0] = 0;
  2.1404 +        for (var bits=1; bits<=maxBits; bits++) {
  2.1405 +            code = (code + bitLengthsCount[bits - 1]) << 1;
  2.1406 +            nextCode[bits] = code;
  2.1407 +        }
  2.1408 +
  2.1409 +        for (i=0; i<codes.length; i++) {
  2.1410 +            var len = lengths[i];
  2.1411 +            if (len !== 0) {
  2.1412 +                codes[i] = nextCode[len];
  2.1413 +                nextCode[len]++;
  2.1414 +            }
  2.1415 +        }
  2.1416 +        return codes;
  2.1417 +    };
  2.1418 +
  2.1419 +    var buildTree = function(codes, lengths){
  2.1420 +        var nonEmptyCodes = [];
  2.1421 +        for(var i=0; i<codes.length; ++i) {
  2.1422 +            if(lengths[i] > 0) {
  2.1423 +                var code = {};
  2.1424 +                code.bits = codes[i];
  2.1425 +                code.length = lengths[i];
  2.1426 +                code.index = i;
  2.1427 +                nonEmptyCodes.push(code);
  2.1428 +            }
  2.1429 +        }
  2.1430 +        return buildTreeBranch(nonEmptyCodes, 0, 0);
  2.1431 +    };
  2.1432 +
  2.1433 +
  2.1434 +    var buildTreeBranch = function(codes, prefix, prefixLength){
  2.1435 +        if (codes.length === 0) return null;
  2.1436 +
  2.1437 +        var zeros = [];
  2.1438 +        var ones = [];
  2.1439 +        var branch = {};
  2.1440 +        branch.isLeaf = false;
  2.1441 +        for(var i=0; i<codes.length; ++i) {
  2.1442 +            if (codes[i].length == prefixLength && codes[i].bits == prefix) {
  2.1443 +                branch.isLeaf = true;
  2.1444 +                branch.index = codes[i].index;
  2.1445 +                break;
  2.1446 +            }
  2.1447 +            else {
  2.1448 +                var nextBit = ((codes[i].bits >> (codes[i].length - prefixLength - 1)) & 1) > 0;
  2.1449 +                if (nextBit)  ones.push(codes[i]);
  2.1450 +                else         zeros.push(codes[i]);
  2.1451 +            }
  2.1452 +        }
  2.1453 +        if(!branch.isLeaf) {
  2.1454 +            branch.zero = buildTreeBranch(zeros, (prefix << 1), prefixLength + 1);
  2.1455 +            branch.one = buildTreeBranch(ones, (prefix << 1) | 1, prefixLength + 1);
  2.1456 +        }
  2.1457 +        return branch;
  2.1458 +    };
  2.1459 +
  2.1460 +
  2.1461 +    var encodedLengthStart = [3,4,5,6,7,8,9,10,
  2.1462 +                              11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,
  2.1463 +                              115,131,163,195,227,258];
  2.1464 +
  2.1465 +    var encodedLengthAdditionalBits = [0,0,0,0,0,0,0,0,
  2.1466 +                                       1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0];
  2.1467 +
  2.1468 +    var encodedDistanceStart = [1,2,3,4, 5,7,9, 13,17,25, 33,49,65,
  2.1469 +                                97,129,193,257,385,513,769,1025,1537,2049,
  2.1470 +                                3073,4097,6145,8193,12289,16385,24577];
  2.1471 +
  2.1472 +    var encodedDistanceAdditionalBits = [0,0,0,0,1,1,2,2,3,3,4,4,
  2.1473 +                                         5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13];
  2.1474 +
  2.1475 +
  2.1476 +    var readDynamicTrees = function(bitReader){
  2.1477 +        var hlit  = bitReader.readLSB(5) + 257;
  2.1478 +        var hdist = bitReader.readLSB(5) + 1;
  2.1479 +        var hclen = bitReader.readLSB(4) + 4;
  2.1480 +        var clen  = new Array(19);
  2.1481 +        var i;
  2.1482 +        for (i=0; i<clen.length; i++) { clen[i]          = 0; }
  2.1483 +        for (i=0; i<hclen;       i++) { clen[clenMap[i]] = bitReader.readLSB(3); }
  2.1484 +
  2.1485 +        var clenCodes = buildCodes(clen);
  2.1486 +        var clenTree  = buildTree(clenCodes, clen);
  2.1487 +
  2.1488 +        var lengthsSequence = [];
  2.1489 +        while(lengthsSequence.length < hlit + hdist) {
  2.1490 +            var p = clenTree;
  2.1491 +            while(!p.isLeaf) { p = bitReader.readBit() ? p.one : p.zero; }
  2.1492 +
  2.1493 +            var code = p.index;
  2.1494 +            if      (code <= 15) lengthsSequence.push(code);
  2.1495 +            else if (code == 16) {
  2.1496 +                // TODO: replace with faster repeat algorythm
  2.1497 +                var repeat = bitReader.readLSB(2) + 3;
  2.1498 +                for(var q=0; q<repeat; ++q){
  2.1499 +                    lengthsSequence.push(lengthsSequence[lengthsSequence.length - 1]);
  2.1500 +                }
  2.1501 +            }
  2.1502 +            else if (code == 17) {
  2.1503 +                var repeat1 = bitReader.readLSB(3) + 3;
  2.1504 +                for(var q1=0; q1<repeat1; ++q1) {
  2.1505 +                    lengthsSequence.push(0);
  2.1506 +                }
  2.1507 +            }
  2.1508 +            else if (code == 18) {
  2.1509 +                var repeat2 = bitReader.readLSB(7) + 11;
  2.1510 +                for(var q2=0; q2<repeat2; ++q2){
  2.1511 +                    lengthsSequence.push(0);
  2.1512 +                }
  2.1513 +            }
  2.1514 +        }
  2.1515 +
  2.1516 +        var codesLengths     = lengthsSequence.slice(0, hlit);
  2.1517 +        var codes            = buildCodes(codesLengths);
  2.1518 +        var distancesLengths = lengthsSequence.slice(hlit, hlit + hdist);
  2.1519 +        var distances        = buildCodes(distancesLengths);
  2.1520 +
  2.1521 +        return {
  2.1522 +            codesTree     : buildTree(codes,     codesLengths),
  2.1523 +            distancesTree : buildTree(distances, distancesLengths)
  2.1524 +        };
  2.1525 +    };
  2.1526 +
  2.1527 +
  2.1528 +    _inflatingReader.prototype = new JSIO._ByteReaderBase();
  2.1529 +
  2.1530 +
  2.1531 +    // internal instance fns
  2.1532 +    _inflatingReader.prototype._decodeItem = function() {
  2.1533 +        if (this._state == 2) return null;  // end-of-blocks
  2.1534 +
  2.1535 +        var item;
  2.1536 +        if(this._state === 0) {
  2.1537 +            this._blockFinal = this._bitReader.readBit();
  2.1538 +            var blockType = this._bitReader.readLSB(2);
  2.1539 +            switch(blockType) {
  2.1540 +            case 0:
  2.1541 +                this._bitReader.align();
  2.1542 +                var len  = this._bitReader.readLSB(16);  // low-byte first, as opposed to readNumber's HBF
  2.1543 +                var nlen = this._bitReader.readLSB(16);
  2.1544 +                if ((len & ~nlen) != len) this._throwError('Invalid block type 0 length', null, '_decodeItem');
  2.1545 +
  2.1546 +                item = {};
  2.1547 +                item.itemType = 0;
  2.1548 +                item.array = this._bitReader.byteReader.read(len);
  2.1549 +                if (item.array.length < len) this._throwError('Incomplete block', null, '_decodeItem');
  2.1550 +                if (this._blockFinal) this._state = 2;
  2.1551 +                return item;
  2.1552 +            case 1:
  2.1553 +                this._codesTree = staticCodes;
  2.1554 +                this._distancesTree = staticDistances;
  2.1555 +                this._state = 1;
  2.1556 +                break;
  2.1557 +            case 2:
  2.1558 +                var dTrees = readDynamicTrees(this._bitReader);
  2.1559 +                this._codesTree = dTrees.codesTree;
  2.1560 +                this._distancesTree = dTrees.distancesTree;
  2.1561 +                this._state = 1;
  2.1562 +                break;
  2.1563 +            default:
  2.1564 +                this._throwError('Invalid block type ('+ blockType +')', null, '_decodeItem');
  2.1565 +            }
  2.1566 +        }
  2.1567 +
  2.1568 +        item = {};
  2.1569 +
  2.1570 +        var p = this._codesTree;
  2.1571 +        while (!p.isLeaf) { p = this._bitReader.readBit() ? p.one : p.zero; }
  2.1572 +        if(p.index < 256) {
  2.1573 +            item.itemType = 2;
  2.1574 +            item.symbol = p.index;
  2.1575 +        } else if(p.index > 256) {
  2.1576 +            var lengthCode = p.index;
  2.1577 +            if(lengthCode > 285) this._throwError('Invalid length code', null, '_decodeItem');
  2.1578 +
  2.1579 +            var length = encodedLengthStart[lengthCode - 257];
  2.1580 +            if(encodedLengthAdditionalBits[lengthCode - 257] > 0) {
  2.1581 +                length += this._bitReader.readLSB(encodedLengthAdditionalBits[lengthCode - 257]);
  2.1582 +            }
  2.1583 +
  2.1584 +            p = this._distancesTree;
  2.1585 +            while (!p.isLeaf) { p = this._bitReader.readBit() ? p.one : p.zero; }
  2.1586 +
  2.1587 +            var distanceCode = p.index;
  2.1588 +            var distance = encodedDistanceStart[distanceCode];
  2.1589 +            if (encodedDistanceAdditionalBits[distanceCode] > 0)
  2.1590 +                distance += this._bitReader.readLSB(encodedDistanceAdditionalBits[distanceCode]);
  2.1591 +
  2.1592 +            item.itemType = 3;
  2.1593 +            item.distance = distance;
  2.1594 +            item.length = length;
  2.1595 +        } else {
  2.1596 +            item.itemType = 1;
  2.1597 +            this._state = this._blockFinal ? 2 : 0;  // EOB
  2.1598 +        }
  2.1599 +        return item;
  2.1600 +    };
  2.1601 +
  2.1602 +
  2.1603 +
  2.1604 +    // public instance functions
  2.1605 +
  2.1606 +    _inflatingReader.prototype.readByte = function() {
  2.1607 +        var byte = this.read(1)[0];
  2.1608 +        return (byte === null || byte === undefined) ? null : byte;
  2.1609 +    };
  2.1610 +
  2.1611 +    _inflatingReader.prototype.read = function(len) {
  2.1612 +        var b = this._buffer;  // (since we use this so much...)
  2.1613 +
  2.1614 +        // Keep reading until we get to the right length
  2.1615 +        while (this._bufferPosition+len > b.length) {
  2.1616 +            var item = this._decodeItem();
  2.1617 +            if (item === null) {  // EOF
  2.1618 +                len = b.length - this._bufferPosition;
  2.1619 +                break;
  2.1620 +            }
  2.1621 +            switch(item.itemType) {
  2.1622 +                case 0:
  2.1623 +                    JSIO.massApply(b.push, b, item.array);
  2.1624 +                    break;
  2.1625 +                case 2:
  2.1626 +                    b.push(item.symbol);
  2.1627 +                    break;
  2.1628 +                case 3:
  2.1629 +                    var j = b.length - item.distance;
  2.1630 +                    if (item.distance >= item.length)
  2.1631 +                        JSIO.massApply(b.push, b, b.slice(j, j+item.length));
  2.1632 +                    // sometimes DEFLATE tries some trickery with "look-ahead" compression
  2.1633 +                    else {
  2.1634 +                        // this is basically just a repetition of the same string, plus some possible cutoff
  2.1635 +                        var count = parseInt(item.length / item.distance);
  2.1636 +                        var repArr = b.slice(j);
  2.1637 +                        // http://stackoverflow.com/questions/202605/repeat-string-javascript/5450113#5450113
  2.1638 +                        while (count > 0) {
  2.1639 +                            if (count   & 1) JSIO.massApply(     b.push,      b, repArr);
  2.1640 +                            if (count >>= 1) JSIO.massApply(repArr.push, repArr, repArr);
  2.1641 +                        }
  2.1642 +                        // add any remaining cutoff
  2.1643 +                        var r;
  2.1644 +                        if (r = item.length % item.distance)
  2.1645 +                            JSIO.massApply(b.push, b, b.slice(j, j+r));
  2.1646 +                    }
  2.1647 +                    break;
  2.1648 +            }
  2.1649 +        }
  2.1650 +        var bytes = b.slice(this._bufferPosition, this._bufferPosition+len);
  2.1651 +        this._bufferPosition += len;
  2.1652 +        this.position        += len;
  2.1653 +
  2.1654 +        if (this._bufferPosition > 0xC000) {
  2.1655 +            var shift = b.length - 0x8000;
  2.1656 +            if (shift > this._bufferPosition) shift = this._bufferPosition;
  2.1657 +            b.splice(0, shift);
  2.1658 +            this._bufferPosition -= shift;
  2.1659 +        }
  2.1660 +
  2.1661 +        return bytes;
  2.1662 +    };
  2.1663 +
  2.1664 +    // initialization routine - once per type
  2.1665 +    (function(){
  2.1666 +
  2.1667 +        var codes = new Array(288);
  2.1668 +        var codesLengths = new Array(288);
  2.1669 +        var i=0;
  2.1670 +        for ( i = 0;   i <= 143; i++) {
  2.1671 +            codes[i] = 0x0030 + i;
  2.1672 +            codesLengths[i] = 8;
  2.1673 +        }
  2.1674 +        for ( i = 144; i <= 255; i++) {
  2.1675 +            codes[i] = 0x0190 + i - 144;
  2.1676 +            codesLengths[i] = 9;
  2.1677 +        }
  2.1678 +        for ( i = 256; i <= 279; i++) {
  2.1679 +            codes[i] = 0x0000 + i - 256;
  2.1680 +            codesLengths[i] = 7;
  2.1681 +        }
  2.1682 +        for ( i = 280; i <= 287; i++) {
  2.1683 +            codes[i] = 0x00C0 + i - 280;
  2.1684 +            codesLengths[i] = 8;
  2.1685 +        }
  2.1686 +        staticCodes = buildTree(codes, codesLengths);
  2.1687 +
  2.1688 +        var distances = new Array(32);
  2.1689 +        var distancesLengths = new Array(32);
  2.1690 +        for ( i = 0; i <= 31; i++) {
  2.1691 +            distances[i] = i;
  2.1692 +            distancesLengths[i] = 5;
  2.1693 +        }
  2.1694 +        staticDistances = buildTree(distances, distancesLengths);
  2.1695 +    })();
  2.1696 +
  2.1697 +
  2.1698 +    JSIO.InflatingReader = _inflatingReader;
  2.1699 +
  2.1700 +})();
  2.1701 +
  2.1702 +
  2.1703 +/// JSIO.InflatingReader.js ends
  2.1704 +
  2.1705 +// Zipfile.js
  2.1706 +// ------------------------------------------------------------------
  2.1707 +//
  2.1708 +// A class that reads Zip files.
  2.1709 +// Depends on the JSIO library functions.
  2.1710 +//
  2.1711 +// =======================================================
  2.1712 +//
  2.1713 +// Copyleft (c) 2010, Dino Chiesa via MS-PL
  2.1714 +// Copyleft (c) 2012, Brendan Byrd via GPL
  2.1715 +//
  2.1716 +// This work is licensed under the GPLv3.
  2.1717 +
  2.1718 +(function(){
  2.1719 +    var version = "2.0 2012Feb";
  2.1720 +    var typename = "Zipfile";
  2.1721 +
  2.1722 +    if (typeof JSIO.BinaryUrlStream !== "function") JSIO.throwError('This extension requires JSIO.BinaryUrlStream.js v2.0', typename);
  2.1723 +    if (typeof JSIO.TextDecoder     !== "object")   JSIO.throwError('This extension requires JSIO.TextDecoder.js v2.0',     typename);
  2.1724 +    if (typeof JSIO.TextReader      !== "function") JSIO.throwError('This extension requires JSIO.TextReader.js v2.0',      typename);
  2.1725 +    if (typeof JSIO.Crc32           !== "function") JSIO.throwError('This extension requires JSIO.Crc32.js v2.0',           typename);
  2.1726 +    if (typeof JSIO.InflatingReader !== "function") JSIO.throwError('This extension requires JSIO.InflatingReader.js v2.0', typename);
  2.1727 +
  2.1728 +    // =======================================================
  2.1729 +    function ZipEntry(zip) {
  2.1730 +        this.zipfile = zip;
  2.1731 +        this._typename = "ZipEntry";
  2.1732 +        this._version = version;
  2.1733 +        this._crcCalculator = null;
  2.1734 +    }
  2.1735 +
  2.1736 +    ZipEntry.prototype._throwError = JSIO.throwError;
  2.1737 +
  2.1738 +    // return byte array or string
  2.1739 +    ZipEntry.prototype.extract = function(callback, asString) {
  2.1740 +        this.contentType = JSIO.guessFileType(this.name);
  2.1741 +        asString = asString || ( this.contentType == JSIO.FileType.Text ||
  2.1742 +                                 this.contentType == JSIO.FileType.XML);
  2.1743 +        var thisEntry = this;
  2.1744 +
  2.1745 +        if (this.compressionMethod !== 0 && this.compressionMethod != 8)
  2.1746 +            this._throwError('Unsupported compression method: ' + this.compressionMethod, null, 'extract');
  2.1747 +
  2.1748 +        var reader = (asString) ? this.openTextReader(thisEntry.utf8 ? JSIO.TextDecoder.UTF8 : JSIO.TextDecoder.ANSI) : this.openBinaryReader();
  2.1749 +
  2.1750 +        // diagnostic purpose only; tag the reader with the entry name
  2.1751 +        reader.zipEntryName = thisEntry.name;
  2.1752 +
  2.1753 +        if (typeof callback != "function") {
  2.1754 +            // synchronous
  2.1755 +            var result = reader.readToEnd();
  2.1756 +            this.verifyCrc32();
  2.1757 +            return result;
  2.1758 +        }
  2.1759 +
  2.1760 +        // asynchronous
  2.1761 +        reader.beginReadToEnd(function(result){
  2.1762 +            try {
  2.1763 +                thisEntry.verifyCrc32();
  2.1764 +                callback(thisEntry, result);
  2.1765 +            }
  2.1766 +            catch (exc1) {
  2.1767 +                callback(thisEntry, exc1);
  2.1768 +            }
  2.1769 +        });
  2.1770 +        return null;
  2.1771 +    };
  2.1772 +
  2.1773 +
  2.1774 +    // open a ByteReader on the entry, which will read binary
  2.1775 +    // content from the compressed stream.
  2.1776 +    ZipEntry.prototype.openBinaryReader = function() {
  2.1777 +        var reader =
  2.1778 +                new JSIO.StreamSegmentReader(this.zipfile.binaryStream,
  2.1779 +                                             this.offset + this.lengthOfHeader,
  2.1780 +                                             this.compressedSize);
  2.1781 +        if (this.compressionMethod === 0) {
  2.1782 +            this._crcCalculator = new JSIO.Crc32Reader(reader);
  2.1783 +        }
  2.1784 +        else {
  2.1785 +            var inflator = new JSIO.InflatingReader(reader);
  2.1786 +            this._crcCalculator = new JSIO.Crc32Reader(inflator);
  2.1787 +        }
  2.1788 +        // Whether compressed or not, the source ByteReader in each case
  2.1789 +        // is wrapped in a second ByteReader object that calculates CRC
  2.1790 +        // as it reads.  That way, after all reading is complete, the
  2.1791 +        // caller can check the calcuated CRC against the expected CRC.
  2.1792 +        return this._crcCalculator;
  2.1793 +    };
  2.1794 +
  2.1795 +    // open a TextReader on the entry, to read text from the
  2.1796 +    // compressed stream.
  2.1797 +    ZipEntry.prototype.openTextReader = function(decoderKind) {
  2.1798 +        var reader = this.openBinaryReader();
  2.1799 +        decoderKind = decoderKind || JSIO.TextDecoder.UTF8;
  2.1800 +        var d = new decoderKind(reader);
  2.1801 +        var textReader = new JSIO.TextReader(d);
  2.1802 +        d._parent = textReader;  // store a reference, for diagnostic purposes only
  2.1803 +        return textReader;
  2.1804 +    };
  2.1805 +
  2.1806 +    // verify the CRC on the entry.
  2.1807 +    // call this after all bytes have been read.
  2.1808 +    ZipEntry.prototype.verifyCrc32 = function() {
  2.1809 +        var computedCrc = this._crcCalculator.crc32();
  2.1810 +        var rc = false;  // CRC FAIL
  2.1811 +        if (this.crc32 != computedCrc) {
  2.1812 +            var msg = "WARNING: CRC check failed: " +
  2.1813 +                "entry(" + this.name + ") " +
  2.1814 +                "computed(" + JSIO.decimalToHexString(computedCrc,8) + ") " +
  2.1815 +                "expected(" + JSIO.decimalToHexString(this.crc32,8) + ") ";
  2.1816 +            this.zipfile.status.push(msg);
  2.1817 +        } else {
  2.1818 +            rc = true;  // OK
  2.1819 +            if (this.zipfile.verbose>2) {
  2.1820 +                this.zipfile.status.push("INFO: CRC check ok: 0x" +
  2.1821 +                                         JSIO.decimalToHexString(this.crc32,8));
  2.1822 +            }
  2.1823 +        }
  2.1824 +        return rc;
  2.1825 +    };
  2.1826 +
  2.1827 +
  2.1828 +    // ctor
  2.1829 +    ZipFile = function(fileUrl, callback, verbosity) {
  2.1830 +        if (! (this instanceof arguments.callee) ) JSIO.throwError('You must use new to instantiate this class', typename, 'ctor');
  2.1831 +
  2.1832 +        this.verbose     = verbosity || 0;
  2.1833 +        this.entries     = [];
  2.1834 +        this.entryNames  = [];
  2.1835 +        this.status      = [];
  2.1836 +        this._version    = version;
  2.1837 +        this._typename   = "ZipFile";
  2.1838 +        this._throwError = JSIO.throwError;
  2.1839 +
  2.1840 +        var thisZipFile = this;
  2.1841 +
  2.1842 +        // Could use a back-tracking reader for the central directory, but
  2.1843 +        // there's no point, since all the zip data is held in memory anyway.
  2.1844 +
  2.1845 +        /* function ReadCentralDirectory(){
  2.1846 +            var posn = thisZipFile.binaryStream.length - 64;
  2.1847 +            var maxSeekback = Math.Max(s.Length - 0x4000, 10);
  2.1848 +            var success = false;
  2.1849 +            var nTries = 0;
  2.1850 +            do
  2.1851 +            {
  2.1852 +                thisZipFile.binaryStream.Seek(posn, SeekOrigin.Begin);
  2.1853 +                var bytesRead = thisZipFile.binaryStream.findSignature(thisZipFile.Signatures.EndOfCentralDirectory);
  2.1854 +                if (bytesRead != -1)
  2.1855 +                    success = true;
  2.1856 +                else
  2.1857 +                {
  2.1858 +                    nTries++;
  2.1859 +                    // increasingly larger
  2.1860 +                    posn -= (32 * (nTries + 1) * nTries);
  2.1861 +                    if (posn < 0) posn = 0;  // BOF
  2.1862 +                }
  2.1863 +            }
  2.1864 +            while (!success && posn > maxSeekback);
  2.1865 +            if (!success) {
  2.1866 +                thisZipFile.status.push("cannot find End of Central Directory");
  2.1867 +                return;
  2.1868 +            }
  2.1869 +        } */
  2.1870 +
  2.1871 +
  2.1872 +        function DateFromPackedFormat(packed) {
  2.1873 +            if (packed == 0xFFFF || packed === 0) return new Date(1995, 0, 1, 0,0,0,0);
  2.1874 +
  2.1875 +            var packedTime = packed & 0x0000ffff;
  2.1876 +            var packedDate = ((packed & 0xffff0000) >> 16);
  2.1877 +
  2.1878 +            var year = 1980 + ((packedDate & 0xFE00) >> 9);
  2.1879 +            var month = ((packedDate & 0x01E0) >> 5) -1;
  2.1880 +            var day = packedDate & 0x001F;
  2.1881 +
  2.1882 +            var hour = (packedTime & 0xF800) >> 11;
  2.1883 +            var minute = (packedTime & 0x07E0) >> 5;
  2.1884 +            var second = (packedTime & 0x001F) * 2;
  2.1885 +
  2.1886 +            // Validation and error checking.
  2.1887 +            // This is not foolproof but will catch most errors.
  2.1888 +
  2.1889 +            // I can't believe how many different ways applications
  2.1890 +            // can mess up a simple date format.
  2.1891 +
  2.1892 +            if (second >= 60) { minute++; second = 0; }
  2.1893 +            if (minute >= 60) { hour++;   minute = 0; }
  2.1894 +            if (hour   >= 24) { day++;    hour   = 0; }
  2.1895 +            var success = false;
  2.1896 +            var d;
  2.1897 +            try {
  2.1898 +                d = new Date(year, month, day, hour, minute, second, 0);
  2.1899 +                success= true;
  2.1900 +            }
  2.1901 +            catch (exc1) {
  2.1902 +                if (year == 1980 && (month === 0 || day === 0)) {
  2.1903 +                    try {
  2.1904 +                        d = new Date(1980, 0, 1, hour, minute, second, 0);
  2.1905 +                        success= true;
  2.1906 +                    }
  2.1907 +                    catch (exc2) {
  2.1908 +                        try {
  2.1909 +                            d = new Date(1980, 0, 1, 0, 0, 0, 0);
  2.1910 +                            success= true;
  2.1911 +                        }
  2.1912 +                        catch (exc3) { }  // how could this fail??
  2.1913 +                    }
  2.1914 +                }
  2.1915 +                else {
  2.1916 +                    try {
  2.1917 +                        if (year   < 1980) year   = 1980;
  2.1918 +                        if (year   > 2030) year   = 2030;
  2.1919 +                        if (month  < 1)    month  = 1;
  2.1920 +                        if (month  > 12)   month  = 12;
  2.1921 +                        if (day    < 1)    day    = 1;
  2.1922 +                        if (day    > 31)   day    = 31;
  2.1923 +                        if (minute < 0)    minute = 0;
  2.1924 +                        if (minute > 59)   minute = 59;
  2.1925 +                        if (second < 0)    second = 0;
  2.1926 +                        if (second > 59)   second = 59;
  2.1927 +                        d = new Date(year, month-1, day, hour, minute, second, 0);
  2.1928 +                        success= true;
  2.1929 +                    }
  2.1930 +                    catch (exc4){}
  2.1931 +                }
  2.1932 +            }
  2.1933 +            if (!success) this._throwError('Bad date/time value in this ZIP file', null, 'DateFromPackedFormat');
  2.1934 +            return d;
  2.1935 +        }
  2.1936 +
  2.1937 +
  2.1938 +        function ReadZipEntries () {
  2.1939 +            // read only once
  2.1940 +            if (thisZipFile.entryNames.length === 0){
  2.1941 +                var e;
  2.1942 +                while ((e = ReadZipEntry()) !== null) {
  2.1943 +                    thisZipFile.entries.push(e);
  2.1944 +                    thisZipFile.entryNames.push(e.name);
  2.1945 +                }
  2.1946 +            }
  2.1947 +        }
  2.1948 +
  2.1949 +
  2.1950 +        function ReadZipEntry () {
  2.1951 +            var offset = thisZipFile.binaryStream.position;
  2.1952 +            var sig = thisZipFile.binaryStream.readNumber(4);
  2.1953 +            if (sig == ZipFile.Signatures.DirEntry) {
  2.1954 +                // after all entries, comes the central directory
  2.1955 +                if (thisZipFile.verbose > 0) {
  2.1956 +                    thisZipFile.status.push("INFO: at offset 0x" +
  2.1957 +                                     JSIO.decimalToHexString(offset) +
  2.1958 +                                     ", found start of Zip Directory.");
  2.1959 +                }
  2.1960 +                // all done reading
  2.1961 +                return null;
  2.1962 +            }
  2.1963 +            if (sig != ZipFile.Signatures.Entry) {
  2.1964 +                thisZipFile.status.push("WARNING: at offset 0x" +
  2.1965 +                                 JSIO.decimalToHexString(offset) +
  2.1966 +                                 ", found unexpected signature: 0x" +
  2.1967 +                                 JSIO.decimalToHexString(sig));
  2.1968 +                return null;
  2.1969 +            }
  2.1970 +
  2.1971 +            var entry = new ZipEntry(thisZipFile);
  2.1972 +            entry.offset            = offset;
  2.1973 +            entry.versionNeeded     = thisZipFile.binaryStream.readNumber(2);
  2.1974 +            entry.bitField          = thisZipFile.binaryStream.readNumber(2);
  2.1975 +            entry.compressionMethod = thisZipFile.binaryStream.readNumber(2);
  2.1976 +            var timeBlob            = thisZipFile.binaryStream.readNumber(4);
  2.1977 +            entry.lastModified      = DateFromPackedFormat(timeBlob);
  2.1978 +            entry.crc32             = thisZipFile.binaryStream.readNumber(4);
  2.1979 +            entry.compressedSize    = thisZipFile.binaryStream.readNumber(4);
  2.1980 +            entry.uncompressedSize  = thisZipFile.binaryStream.readNumber(4);
  2.1981 +
  2.1982 +            if ((entry.bitField & 0x01) == 0x01){
  2.1983 +                thisZipFile.status.push("This zipfile uses Encryption, which is not supported by ZipFile.js.");
  2.1984 +                return null;
  2.1985 +            }
  2.1986 +
  2.1987 +            entry.utf8 = ((entry.bitField & 0x0800) == 0x0800);
  2.1988 +
  2.1989 +            if ((entry.bitField & 0x0008) == 0x0008){
  2.1990 +                thisZipFile.status.push("This zipfile uses a bit 3 trailing data descriptor, which is not supported by ZipFile.js.");
  2.1991 +                return null;
  2.1992 +            }
  2.1993 +
  2.1994 +            if (entry.compressedSize == 0xFFFFFFFF ||
  2.1995 +                entry.uncompressedSize == 0xFFFFFFFF) {
  2.1996 +                thisZipFile.status.push("This zipfile uses ZIP64, which is not supported by ZipFile.js");
  2.1997 +                return null;
  2.1998 +            }
  2.1999 +
  2.2000 +            var filenameLength   = thisZipFile.binaryStream.readNumber(2);
  2.2001 +            var extraFieldLength = thisZipFile.binaryStream.readNumber(2);
  2.2002 +
  2.2003 +            thisZipFile.status.push("INFO: filename length= " + filenameLength);
  2.2004 +
  2.2005 +            // we've read 30 bytes of metadata so far
  2.2006 +            var bytesRead = 30 + filenameLength + extraFieldLength;
  2.2007 +
  2.2008 +            if (entry.utf8) {
  2.2009 +                thisZipFile.status.push("INFO: before filename, position= 0x" +
  2.2010 +                                        JSIO.decimalToHexString( thisZipFile.binaryStream.position ));
  2.2011 +                var binReader =
  2.2012 +                    new JSIO.StreamSegmentReader(thisZipFile.binaryStream,
  2.2013 +                                                 thisZipFile.binaryStream.position,
  2.2014 +                                                 filenameLength);
  2.2015 +                var utf8Decoder = new JSIO.TextDecoder.UTF8(binReader);
  2.2016 +                var textReader  = new JSIO.TextReader(utf8Decoder);
  2.2017 +                entry.name = textReader.readToEnd();
  2.2018 +
  2.2019 +                // advance the filepointer:
  2.2020 +                thisZipFile.binaryStream.seek(filenameLength,
  2.2021 +                                              JSIO.SeekOrigin.Current,
  2.2022 +                                              thisZipFile);
  2.2023 +
  2.2024 +                thisZipFile.status.push("INFO: after filename, position= 0x" +
  2.2025 +                                        JSIO.decimalToHexString( thisZipFile.binaryStream.position ));
  2.2026 +            }
  2.2027 +            else {
  2.2028 +                entry.name = thisZipFile.binaryStream.readString(filenameLength);
  2.2029 +            }
  2.2030 +
  2.2031 +            // There are a bunch of things in the "extra" header, thisZipFile we
  2.2032 +            // could parse, like timestamps and other things.  This class
  2.2033 +            // only identifies and separates them.
  2.2034 +
  2.2035 +            // More info here: http://www.pkware.com/documents/casestudies/APPNOTE.TXT
  2.2036 +
  2.2037 +            var extraPos = 0;
  2.2038 +            entry.extra = [];
  2.2039 +            while (extraPos < extraFieldLength) {
  2.2040 +                var extraBlock = {
  2.2041 +                    type: thisZipFile.binaryStream.readNumber(2),
  2.2042 +                    size: thisZipFile.binaryStream.readNumber(2)
  2.2043 +                };
  2.2044 +                extraBlock.typeDescription = ZipFile.ExtraFieldTypes[extraBlock.type];
  2.2045 +                extraBlock.data = thisZipFile.binaryStream.read(extraBlock.size);
  2.2046 +                entry.extra.push(extraBlock);
  2.2047 +                extraPos += 4 + extraBlock.size;
  2.2048 +            }
  2.2049 +
  2.2050 +            if (thisZipFile.verbose > 1) {
  2.2051 +                thisZipFile.status.push("INFO: at offset 0x" +
  2.2052 +                             JSIO.decimalToHexString(entry.offset) +
  2.2053 +                             ", found entry '" + entry.name + "' fnl(" +
  2.2054 +                             filenameLength + ") efl(" +
  2.2055 +                             extraFieldLength +")");
  2.2056 +            }
  2.2057 +
  2.2058 +            if (extraFieldLength > 0) {
  2.2059 +                if (thisZipFile.verbose > 0) {
  2.2060 +                    thisZipFile.status.push("INFO: entry " + entry.name + " has " +
  2.2061 +                                     extraFieldLength + " bytes of " +
  2.2062 +                                     "extra metadata (ID'd but ignored)");
  2.2063 +                }
  2.2064 +            }
  2.2065 +
  2.2066 +            entry.lengthOfHeader = bytesRead;
  2.2067 +            entry.totalEntrySize = entry.lengthOfHeader + entry.compressedSize;
  2.2068 +
  2.2069 +            // seek past the data without reading it. We will read on Extract()
  2.2070 +            if (thisZipFile.verbose > 1) {
  2.2071 +                thisZipFile.status.push("INFO: seek 0x" +
  2.2072 +                                 JSIO.decimalToHexString(entry.compressedSize) +
  2.2073 +                                 " (" + entry.compressedSize + ") bytes");
  2.2074 +            }
  2.2075 +
  2.2076 +            thisZipFile.binaryStream.seek(entry.compressedSize,
  2.2077 +                              JSIO.SeekOrigin.Current,
  2.2078 +                              thisZipFile);
  2.2079 +
  2.2080 +            return entry;
  2.2081 +        }
  2.2082 +
  2.2083 +
  2.2084 +        var parseZipFile = function(bfr){
  2.2085 +            try {
  2.2086 +                if (bfr.req.status == 200) {
  2.2087 +                    var sig = thisZipFile.binaryStream.readNumber(4);
  2.2088 +                    if (sig != ZipFile.Signatures.Entry){
  2.2089 +                        thisZipFile.status.push("WARNING: this file does not appear to be a zip file");
  2.2090 +                    } else {
  2.2091 +                        thisZipFile.binaryStream.seek(0, JSIO.SeekOrigin.Begin);
  2.2092 +                        ReadZipEntries();
  2.2093 +                        if (thisZipFile.verbose > 0) {
  2.2094 +                            thisZipFile.status.push("INFO: read " + thisZipFile.entries.length + " entries");
  2.2095 +                        }
  2.2096 +                    }
  2.2097 +                }
  2.2098 +                else {
  2.2099 +                    thisZipFile.status.push("ERROR: the URL could not be read (" +
  2.2100 +                                     bfr.req.status + " " + bfr.req.statusText + ")");
  2.2101 +                }
  2.2102 +                callback(thisZipFile);
  2.2103 +            }
  2.2104 +            catch (exc1)
  2.2105 +            {
  2.2106 +                thisZipFile.status.push("Exception: " + exc1.message);
  2.2107 +                callback(thisZipFile);
  2.2108 +            }
  2.2109 +        };
  2.2110 +
  2.2111 +        this.binaryStream = new JSIO.BinaryUrlStream(fileUrl, parseZipFile);
  2.2112 +
  2.2113 +        return this;
  2.2114 +    };
  2.2115 +
  2.2116 +
  2.2117 +    ZipFile.Signatures = {
  2.2118 +        Entry                 : 0x04034b50,
  2.2119 +        EndOfCentralDirectory : 0x06054b50,
  2.2120 +        DirEntry              : 0x02014b50
  2.2121 +    };
  2.2122 +
  2.2123 +    ZipFile.Version = version;
  2.2124 +
  2.2125 +    ZipFile.EncryptionAlgorithm = {
  2.2126 +        None      : 0,
  2.2127 +        PkzipWeak : 1,
  2.2128 +        WinZipAes : 2
  2.2129 +    };
  2.2130 +
  2.2131 +    ZipFile.ExtraFieldTypes = {};
  2.2132 +    ZipFile.ExtraFieldTypes[0x0001] = 'Zip64 Extended Info';
  2.2133 +    ZipFile.ExtraFieldTypes[0x0007] = 'AV Info';
  2.2134 +    ZipFile.ExtraFieldTypes[0x0008] = 'Extended Language Encoding Data (PFS)';
  2.2135 +    ZipFile.ExtraFieldTypes[0x0009] = 'OS/2';
  2.2136 +    ZipFile.ExtraFieldTypes[0x000a] = 'NTFS ';
  2.2137 +    ZipFile.ExtraFieldTypes[0x000c] = 'OpenVMS';
  2.2138 +    ZipFile.ExtraFieldTypes[0x000d] = 'UNIX';
  2.2139 +    ZipFile.ExtraFieldTypes[0x000e] = 'File Stream and Fork Descriptors';
  2.2140 +    ZipFile.ExtraFieldTypes[0x000f] = 'Patch Descriptor';
  2.2141 +    ZipFile.ExtraFieldTypes[0x0014] = 'PKCS#7 Store for X.509 Certificates';
  2.2142 +    ZipFile.ExtraFieldTypes[0x0015] = 'X.509 Certificate ID and Signature (Individual File)';
  2.2143 +    ZipFile.ExtraFieldTypes[0x0016] = 'X.509 Certificate ID (Central Directory)';
  2.2144 +    ZipFile.ExtraFieldTypes[0x0017] = 'Strong Encryption Header';
  2.2145 +    ZipFile.ExtraFieldTypes[0x0018] = 'Record Management Controls';
  2.2146 +    ZipFile.ExtraFieldTypes[0x0019] = 'PKCS#7 Encryption Recipient Certificate List';
  2.2147 +    ZipFile.ExtraFieldTypes[0x0065] = 'IBM S/390 (Z390), AS/400 (I400) attributes (uncompressed)';
  2.2148 +    ZipFile.ExtraFieldTypes[0x0066] = 'IBM S/390 (Z390), AS/400 (I400) attributes (compressed)';
  2.2149 +    ZipFile.ExtraFieldTypes[0x4690] = 'POSZIP 4690 (reserved) ';
  2.2150 +    ZipFile.ExtraFieldTypes[0x07c8] = 'Macintosh';
  2.2151 +    ZipFile.ExtraFieldTypes[0x2605] = 'ZipIt Macintosh';
  2.2152 +    ZipFile.ExtraFieldTypes[0x2705] = 'ZipIt Macintosh 1.3.5+';
  2.2153 +    ZipFile.ExtraFieldTypes[0x2805] = 'ZipIt Macintosh 1.3.5+';
  2.2154 +    ZipFile.ExtraFieldTypes[0x334d] = 'Info-ZIP Macintosh';
  2.2155 +    ZipFile.ExtraFieldTypes[0x4341] = 'Acorn/SparkFS ';
  2.2156 +    ZipFile.ExtraFieldTypes[0x4453] = 'Windows NT security descriptor (binary ACL)';
  2.2157 +    ZipFile.ExtraFieldTypes[0x4704] = 'VM/CMS';
  2.2158 +    ZipFile.ExtraFieldTypes[0x470f] = 'MVS';
  2.2159 +    ZipFile.ExtraFieldTypes[0x4b46] = 'FWKCS MD5';
  2.2160 +    ZipFile.ExtraFieldTypes[0x4c41] = 'OS/2 access control list (text ACL)';
  2.2161 +    ZipFile.ExtraFieldTypes[0x4d49] = 'Info-ZIP OpenVMS';
  2.2162 +    ZipFile.ExtraFieldTypes[0x4f4c] = 'Xceed original location extra field';
  2.2163 +    ZipFile.ExtraFieldTypes[0x5356] = 'AOS/VS (ACL)';
  2.2164 +    ZipFile.ExtraFieldTypes[0x5455] = 'extended timestamp';
  2.2165 +    ZipFile.ExtraFieldTypes[0x554e] = 'Xceed unicode extra field';
  2.2166 +    ZipFile.ExtraFieldTypes[0x5855] = 'Info-ZIP UNIX (original, also OS/2, NT, etc)';
  2.2167 +    ZipFile.ExtraFieldTypes[0x6375] = 'Info-ZIP Unicode Comment Extra Field';
  2.2168 +    ZipFile.ExtraFieldTypes[0x6542] = 'BeOS/BeBox';
  2.2169 +    ZipFile.ExtraFieldTypes[0x7075] = 'Info-ZIP Unicode Path Extra Field';
  2.2170 +    ZipFile.ExtraFieldTypes[0x756e] = 'ASi UNIX';
  2.2171 +    ZipFile.ExtraFieldTypes[0x7855] = 'Info-ZIP UNIX (new)';
  2.2172 +    ZipFile.ExtraFieldTypes[0xa220] = 'Microsoft Open Packaging Growth Hint';
  2.2173 +    ZipFile.ExtraFieldTypes[0xfd4a] = 'SMS/QDOS';
  2.2174 +
  2.2175 +})();
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/endpoint/WebContent/js/geoxml3-kmz.js	Fri Sep 14 13:03:24 2012 +0300
     3.3 @@ -0,0 +1,1889 @@
     3.4 +/**
     3.5 + * @fileOverview Renders KML on the Google Maps JavaScript API Version 3
     3.6 + * @name GeoXML3
     3.7 + * @author Sterling Udell, Larry Ross, Brendan Byrd
     3.8 + * @see http://code.google.com/p/geoxml3/
     3.9 + *
    3.10 + * geoxml3.js
    3.11 + *
    3.12 + * Renders KML on the Google Maps JavaScript API Version 3
    3.13 + * http://code.google.com/p/geoxml3/
    3.14 + *
    3.15 + * Copyright 2010 Sterling Udell, Larry Ross
    3.16 + *
    3.17 + * Licensed under the Apache License, Version 2.0 (the "License");
    3.18 + * you may not use this file except in compliance with the License.
    3.19 + * You may obtain a copy of the License at
    3.20 + *
    3.21 + *    http://www.apache.org/licenses/LICENSE-2.0
    3.22 + *
    3.23 + * Unless required by applicable law or agreed to in writing, software
    3.24 + * distributed under the License is distributed on an "AS IS" BASIS,
    3.25 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    3.26 + * See the License for the specific language governing permissions and
    3.27 + * limitations under the License.
    3.28 + *
    3.29 + */
    3.30 +
    3.31 +if (!String.prototype.trim) {
    3.32 +/**
    3.33 + * Remove leading and trailing whitespace.
    3.34 + *
    3.35 + * @augments String
    3.36 + * @return {String}
    3.37 + */
    3.38 +  String.prototype.trim = function () {
    3.39 +    return this.replace(/^\s+|\s+$/g, '');
    3.40 +  };
    3.41 +}
    3.42 +
    3.43 +/**
    3.44 + * @namespace The GeoXML3 namespace.
    3.45 + */
    3.46 +geoXML3 = window.geoXML3 || {instances: []};
    3.47 +
    3.48 +/**
    3.49 + * Constructor for the root KML parser object.
    3.50 + *
    3.51 + * <p>All top-level objects and functions are declared under a namespace of geoXML3.
    3.52 + * The core object is geoXML3.parser; typically, you'll instantiate a one parser
    3.53 + * per map.</p>
    3.54 + *
    3.55 + * @class Main XML parser.
    3.56 + * @param {geoXML3.parserOptions} options
    3.57 + */
    3.58 +geoXML3.parser = function (options) {
    3.59 +  // Private variables
    3.60 +  var parserOptions = new geoXML3.parserOptions(options);
    3.61 +  var docs        = [];  // Individual KML documents
    3.62 +  var docsByUrl   = {};  // Same docs as an hash by cleanURL
    3.63 +  var kmzMetaData = {};  // Extra files from KMZ data
    3.64 +  var styles      = {};  // Global list of styles
    3.65 +  var lastPlacemark;
    3.66 +  var parserName;
    3.67 +  if (!parserOptions.infoWindow && parserOptions.singleInfoWindow)
    3.68 +    parserOptions.infoWindow = new google.maps.InfoWindow();
    3.69 +
    3.70 +  var parseKmlString = function (kmlString, docSet) {
    3.71 +    // Internal values for the set of documents as a whole
    3.72 +    var internals = {
    3.73 +      parser: this,
    3.74 +      docSet: docSet || [],
    3.75 +      remaining: 1,
    3.76 +      parseOnly: !(parserOptions.afterParse || parserOptions.processStyles)
    3.77 +    };
    3.78 +    thisDoc = new Object();
    3.79 +    thisDoc.internals = internals;
    3.80 +    internals.docSet.push(thisDoc);
    3.81 +    render(geoXML3.xmlParse(kmlString),thisDoc);
    3.82 +  }
    3.83 +
    3.84 +  var parse = function (urls, docSet) {
    3.85 +    // Process one or more KML documents
    3.86 +    if (!parserName) {
    3.87 +      parserName = 'geoXML3.instances[' + (geoXML3.instances.push(this) - 1) + ']';
    3.88 +    }
    3.89 +
    3.90 +    if (typeof urls === 'string') {
    3.91 +      // Single KML document
    3.92 +      urls = [urls];
    3.93 +    }
    3.94 +
    3.95 +    // Internal values for the set of documents as a whole
    3.96 +    var internals = {
    3.97 +      parser: this,
    3.98 +      docSet: docSet || [],
    3.99 +      remaining: urls.length,
   3.100 +      parseOnly: !(parserOptions.afterParse || parserOptions.processStyles)
   3.101 +    };
   3.102 +    var thisDoc, j;
   3.103 +    for (var i = 0; i < urls.length; i++) {
   3.104 +      var baseUrl = cleanURL(defileURL(location.pathname), urls[i]);
   3.105 +      if (docsByUrl[baseUrl]) {
   3.106 +        // Reloading an existing document
   3.107 +        thisDoc = docsByUrl[baseUrl];
   3.108 +        thisDoc.reload = true;
   3.109 +      }
   3.110 +      else {
   3.111 +        thisDoc = new Object();
   3.112 +        thisDoc.baseUrl = baseUrl;
   3.113 +        internals.docSet.push(thisDoc);
   3.114 +      }
   3.115 +      thisDoc.url       = urls[i];
   3.116 +      thisDoc.internals = internals;
   3.117 +      fetchDoc(thisDoc.url, thisDoc);
   3.118 +    }
   3.119 +  };
   3.120 +
   3.121 +  function fetchDoc(url, doc, resFunc) {
   3.122 +    resFunc = resFunc || function (responseXML) { render(responseXML, doc); };
   3.123 +
   3.124 +    if (typeof ZipFile === 'function' && typeof JSIO === 'object' && typeof JSIO.guessFileType === 'function') {  // KMZ support requires these modules loaded
   3.125 +      contentType = JSIO.guessFileType(doc.baseUrl);
   3.126 +      if (contentType == JSIO.FileType.Binary || contentType == JSIO.FileType.Unknown) {
   3.127 +         doc.isCompressed = true;
   3.128 +         doc.baseDir = doc.baseUrl + '/';
   3.129 +         geoXML3.fetchZIP(url, resFunc, doc.internals.parser);
   3.130 +         return;
   3.131 +      }
   3.132 +    }
   3.133 +    doc.isCompressed = false;
   3.134 +    doc.baseDir = defileURL(doc.baseUrl);
   3.135 +    geoXML3.fetchXML(url, resFunc);
   3.136 +  }
   3.137 +
   3.138 +  var hideDocument = function (doc) {
   3.139 +    if (!doc) doc = docs[0];
   3.140 +    // Hide the map objects associated with a document
   3.141 +    var i;
   3.142 +    if (!!doc.markers) {
   3.143 +      for (i = 0; i < doc.markers.length; i++) {
   3.144 +        if(!!doc.markers[i].infoWindow) doc.markers[i].infoWindow.close();
   3.145 +        doc.markers[i].setVisible(false);
   3.146 +      }
   3.147 +    }
   3.148 +    if (!!doc.ggroundoverlays) {
   3.149 +      for (i = 0; i < doc.ggroundoverlays.length; i++) {
   3.150 +        doc.ggroundoverlays[i].setOpacity(0);
   3.151 +      }
   3.152 +    }
   3.153 +    if (!!doc.gpolylines) {
   3.154 +      for (i=0;i<doc.gpolylines.length;i++) {
   3.155 +        if(!!doc.gpolylines[i].infoWindow) doc.gpolylines[i].infoWindow.close();
   3.156 +        doc.gpolylines[i].setMap(null);
   3.157 +      }
   3.158 +    }
   3.159 +    if (!!doc.gpolygons) {
   3.160 +      for (i=0;i<doc.gpolygons.length;i++) {
   3.161 +        if(!!doc.gpolygons[i].infoWindow) doc.gpolygons[i].infoWindow.close();
   3.162 +        doc.gpolygons[i].setMap(null);
   3.163 +      }
   3.164 +    }
   3.165 +  };
   3.166 +
   3.167 +  var showDocument = function (doc) {
   3.168 +    if (!doc) doc = docs[0];
   3.169 +    // Show the map objects associated with a document
   3.170 +    var i;
   3.171 +    if (!!doc.markers) {
   3.172 +      for (i = 0; i < doc.markers.length; i++) {
   3.173 +        doc.markers[i].setVisible(true);
   3.174 +      }
   3.175 +    }
   3.176 +    if (!!doc.ggroundoverlays) {
   3.177 +      for (i = 0; i < doc.ggroundoverlays.length; i++) {
   3.178 +        doc.ggroundoverlays[i].setOpacity(doc.ggroundoverlays[i].percentOpacity_);
   3.179 +      }
   3.180 +    }
   3.181 +    if (!!doc.gpolylines) {
   3.182 +      for (i=0;i<doc.gpolylines.length;i++) {
   3.183 +        doc.gpolylines[i].setMap(parserOptions.map);
   3.184 +      }
   3.185 +    }
   3.186 +    if (!!doc.gpolygons) {
   3.187 +      for (i=0;i<doc.gpolygons.length;i++) {
   3.188 +        doc.gpolygons[i].setMap(parserOptions.map);
   3.189 +      }
   3.190 +    }
   3.191 +  };
   3.192 +
   3.193 +  var defaultStyle = {
   3.194 +    balloon: {
   3.195 +      bgColor:   'ffffffff',
   3.196 +      textColor: 'ff000000',
   3.197 +      text: "<h3>$[name]</h3>\n<div>$[description]</div>\n<div>$[geDirections]</div>",
   3.198 +      displayMode: 'default'
   3.199 +    },
   3.200 +    icon: {
   3.201 +      scale: 1.0,
   3.202 +      dim: {
   3.203 +        x: 0,
   3.204 +        y: 0,
   3.205 +        w: -1,
   3.206 +        h: -1
   3.207 +      },
   3.208 +      hotSpot: {
   3.209 +        x: 0.5,
   3.210 +        y: 0.5,
   3.211 +        xunits: 'fraction',
   3.212 +        yunits: 'fraction'
   3.213 +      }
   3.214 +    },
   3.215 +    line: {
   3.216 +      color: 'ffffffff', // white (KML default)
   3.217 +      colorMode: 'normal',
   3.218 +      width: 1.0
   3.219 +    },
   3.220 +    poly: {
   3.221 +      color: 'ffffffff', // white (KML default)
   3.222 +      colorMode: 'normal',
   3.223 +      fill: true,
   3.224 +      outline: true
   3.225 +    }
   3.226 +  };
   3.227 +
   3.228 +  var kmlNS = 'http://www.opengis.net/kml/2.2';
   3.229 +  var gxNS  = 'http://www.google.com/kml/ext/2.2';
   3.230 +  var nodeValue              = geoXML3.nodeValue;
   3.231 +  var getBooleanValue        = geoXML3.getBooleanValue;
   3.232 +  var getElementsByTagNameNS = geoXML3.getElementsByTagNameNS;
   3.233 +  var getElementsByTagName   = geoXML3.getElementsByTagName;
   3.234 +
   3.235 +function processStyleUrl(node) {
   3.236 +  var styleUrlStr = nodeValue(getElementsByTagName(node, 'styleUrl')[0]);
   3.237 +  if (!!styleUrlStr && styleUrlStr.indexOf('#') != -1) 
   3.238 +    var styleUrl = styleUrlStr.split('#');
   3.239 +  else var styleUrl = ["",""];
   3.240 +  return styleUrl;
   3.241 +}
   3.242 +
   3.243 +  function processStyle(thisNode, baseUrl, styleID, baseDir) {
   3.244 +    var style = (baseUrl === '{inline}') ? clone(defaultStyle) : (styles[baseUrl][styleID] = styles[baseUrl][styleID] || clone(defaultStyle));
   3.245 +
   3.246 +    var styleNodes = getElementsByTagName(thisNode, 'BalloonStyle');
   3.247 +    if (!!styleNodes && styleNodes.length > 0) {
   3.248 +      style.balloon.bgColor     = nodeValue(getElementsByTagName(styleNodes[0], 'bgColor')[0],     style.balloon.bgColor);
   3.249 +      style.balloon.textColor   = nodeValue(getElementsByTagName(styleNodes[0], 'textColor')[0],   style.balloon.textColor);
   3.250 +      style.balloon.text        = nodeValue(getElementsByTagName(styleNodes[0], 'text')[0],        style.balloon.text);
   3.251 +      style.balloon.displayMode = nodeValue(getElementsByTagName(styleNodes[0], 'displayMode')[0], style.balloon.displayMode);
   3.252 +    }
   3.253 +
   3.254 +    // style.list = (unsupported; doesn't make sense in Google Maps)
   3.255 +
   3.256 +    var styleNodes = getElementsByTagName(thisNode, 'IconStyle');
   3.257 +    if (!!styleNodes && styleNodes.length > 0) {
   3.258 +      var icon = style.icon;
   3.259 +
   3.260 +      icon.scale = parseFloat(nodeValue(getElementsByTagName(styleNodes[0], 'scale')[0], icon.scale));
   3.261 +      // style.icon.heading   = (unsupported; not supported in API)
   3.262 +      // style.icon.color     = (unsupported; not supported in API)
   3.263 +      // style.icon.colorMode = (unsupported; not supported in API)
   3.264 +
   3.265 +      styleNodes = getElementsByTagName(thisNode, 'Icon');
   3.266 +      if (!!styleNodes && styleNodes.length > 0) {
   3.267 +        icon.href = nodeValue(getElementsByTagName(styleNodes[0], 'href')[0]);
   3.268 +        icon.url  = cleanURL(baseDir, icon.href);
   3.269 +        // Detect images buried in KMZ files (and use a base64 encoded URL)
   3.270 +        if (kmzMetaData[icon.url]) icon.url = kmzMetaData[icon.url].dataUrl;
   3.271 +
   3.272 +        // Support for icon palettes and exact size dimensions
   3.273 +        icon.dim = {
   3.274 +          x: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'x')[0], icon.dim.x)),
   3.275 +          y: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'y')[0], icon.dim.y)),
   3.276 +          w: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'w')[0], icon.dim.w)),
   3.277 +          h: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'h')[0], icon.dim.h))
   3.278 +        };
   3.279 +
   3.280 +        styleNodes = getElementsByTagName(styleNodes[0], 'hotSpot')[0];
   3.281 +        if (!!styleNodes && styleNodes.length > 0) {
   3.282 +          icon.hotSpot = {
   3.283 +            x:      styleNodes[0].getAttribute('x'),
   3.284 +            y:      styleNodes[0].getAttribute('y'),
   3.285 +            xunits: styleNodes[0].getAttribute('xunits'),
   3.286 +            yunits: styleNodes[0].getAttribute('yunits')
   3.287 +          };
   3.288 +        }
   3.289 +
   3.290 +        // certain occasions where we need the pixel size of the image (like the default settings...)
   3.291 +        // (NOTE: Scale is applied to entire image, not just the section of the icon palette.  So,
   3.292 +        //  if we need scaling, we'll need the img dimensions no matter what.)
   3.293 +        if ( (icon.dim.w < 0 || icon.dim.h < 0) && (icon.xunits != 'pixels' || icon.yunits == 'fraction') || icon.scale != 1.0) {
   3.294 +          // (hopefully, this will load by the time we need it...)
   3.295 +          icon.img = new Image();
   3.296 +          icon.img.onload = function() {
   3.297 +            if (icon.dim.w < 0 || icon.dim.h < 0) {
   3.298 +              icon.dim.w = this.width;
   3.299 +              icon.dim.h = this.height;
   3.300 +            }
   3.301 +          };
   3.302 +          icon.img.src = icon.url;
   3.303 +
   3.304 +          // sometimes the file is already cached and it never calls onLoad
   3.305 +          if (icon.img.width > 0) {
   3.306 +            icon.dim.w = icon.img.width;
   3.307 +            icon.dim.h = icon.img.height;
   3.308 +          }
   3.309 +        }
   3.310 +      }
   3.311 +    }
   3.312 +
   3.313 +    // style.label = (unsupported; may be possible but not with API)
   3.314 +
   3.315 +    styleNodes = getElementsByTagName(thisNode, 'LineStyle');
   3.316 +    if (!!styleNodes && styleNodes.length > 0) {
   3.317 +      style.line.color     = nodeValue(getElementsByTagName(styleNodes[0], 'color')[0],     style.line.color);
   3.318 +      style.line.colorMode = nodeValue(getElementsByTagName(styleNodes[0], 'colorMode')[0], style.line.colorMode);
   3.319 +      style.line.width     = nodeValue(getElementsByTagName(styleNodes[0], 'width')[0],     style.line.width);
   3.320 +      // style.line.outerColor      = (unsupported; not supported in API)
   3.321 +      // style.line.outerWidth      = (unsupported; not supported in API)
   3.322 +      // style.line.physicalWidth   = (unsupported; unneccesary in Google Maps)
   3.323 +      // style.line.labelVisibility = (unsupported; possible to implement)
   3.324 +    }
   3.325 +
   3.326 +    styleNodes = getElementsByTagName(thisNode, 'PolyStyle');
   3.327 +    if (!!styleNodes && styleNodes.length > 0) {
   3.328 +      style.poly.color     = nodeValue(      getElementsByTagName(styleNodes[0], 'color')[0],     style.poly.color);
   3.329 +      style.poly.colorMode = nodeValue(      getElementsByTagName(styleNodes[0], 'colorMode')[0], style.poly.colorMode);
   3.330 +      style.poly.outline   = getBooleanValue(getElementsByTagName(styleNodes[0], 'outline')[0],   style.poly.outline);
   3.331 +      style.poly.fill      = getBooleanValue(getElementsByTagName(styleNodes[0], 'fill')[0],      style.poly.fill);
   3.332 +    }
   3.333 +    return style;
   3.334 +  }
   3.335 +
   3.336 +  // from http://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-clone-a-javascript-object
   3.337 +  // http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
   3.338 +  function clone(obj){
   3.339 +    if(obj == null || typeof(obj) != 'object') return obj;
   3.340 +    if (obj.cloneNode) return obj.cloneNode(true);
   3.341 +    var temp = new obj.constructor();
   3.342 +    for(var key in obj) temp[key] = clone(obj[key]);
   3.343 +    return temp;
   3.344 +  }
   3.345 +
   3.346 +  function processStyleMap(thisNode, baseUrl, styleID, baseDir) {
   3.347 +    var pairs = getElementsByTagName(thisNode, 'Pair');
   3.348 +    var map = new Object();
   3.349 +
   3.350 +    // add each key to the map
   3.351 +    for (var pr=0;pr<pairs.length;pr++) {
   3.352 +      var pairKey      = nodeValue(getElementsByTagName(pairs[pr], 'key')[0]);
   3.353 +      var pairStyle    = nodeValue(getElementsByTagName(pairs[pr], 'Style')[0]);
   3.354 +      var pairStyleUrl = processStyleUrl(pairs[pr]);
   3.355 +      var pairStyleBaseUrl = pairStyleUrl[0] ? cleanURL(baseDir, pairStyleUrl[0]) : baseUrl;
   3.356 +      var pairStyleID      = pairStyleUrl[1];
   3.357 +
   3.358 +      if (!!pairStyle) {
   3.359 +        map[pairKey] = processStyle(pairStyle, pairStyleBaseUrl, pairStyleID);
   3.360 +      } else if (!!pairStyleID && !!styles[pairStyleBaseUrl][pairStyleID]) {
   3.361 +        map[pairKey] = clone(styles[pairStyleBaseUrl][pairStyleID]);
   3.362 +      }
   3.363 +    }
   3.364 +    if (!!map["normal"]) {
   3.365 +      styles[baseUrl][styleID] = clone(map["normal"]);
   3.366 +    } else {
   3.367 +      styles[baseUrl][styleID] = clone(defaultStyle);
   3.368 +    }
   3.369 +    if (!!map["highlight"]) {
   3.370 +      processStyleID(map["highlight"]);
   3.371 +    }
   3.372 +    styles[baseUrl][styleID].map = clone(map);
   3.373 +  }
   3.374 +
   3.375 +  function processPlacemarkCoords(node, tag) {
   3.376 +    var parent = getElementsByTagName(node, tag);
   3.377 +    var coordListA = [];
   3.378 +    for (var i=0; i<parent.length; i++) {
   3.379 +      var coordNodes = getElementsByTagName(parent[i], 'coordinates');
   3.380 +      if (!coordNodes) {
   3.381 +        if (coordListA.length > 0) {
   3.382 +          break;
   3.383 +        } else {
   3.384 +          return [{coordinates: []}];
   3.385 +        }
   3.386 +      }
   3.387 +
   3.388 +      for (var j=0; j<coordNodes.length;j++) {
   3.389 +        var coords = nodeValue(coordNodes[j]).trim();
   3.390 +        coords = coords.replace(/,\s+/g, ',');
   3.391 +        var path = coords.split(/\s+/g);
   3.392 +        var pathLength = path.length;
   3.393 +        var coordList = [];
   3.394 +        for (var k = 0; k < pathLength; k++) {
   3.395 +          coords = path[k].split(',');
   3.396 +          if (!isNaN(coords[0]) && !isNaN(coords[1])) {
   3.397 +            coordList.push({
   3.398 +              lat: parseFloat(coords[1]),
   3.399 +              lng: parseFloat(coords[0]),
   3.400 +              alt: parseFloat(coords[2])
   3.401 +            });
   3.402 +          }
   3.403 +        }
   3.404 +        coordListA.push({coordinates: coordList});
   3.405 +      }
   3.406 +    }
   3.407 +    return coordListA;
   3.408 +  }
   3.409 +
   3.410 +  var render = function (responseXML, doc) {
   3.411 +    // Callback for retrieving a KML document: parse the KML and display it on the map
   3.412 +    if (!responseXML) {
   3.413 +      // Error retrieving the data
   3.414 +      geoXML3.log('Unable to retrieve ' + doc.url);
   3.415 +      if (parserOptions.failedParse) parserOptions.failedParse(doc);
   3.416 +      doc.failed = true;
   3.417 +      return;
   3.418 +    } else if (responseXML.parseError && responseXML.parseError.errorCode != 0) {
   3.419 +      // IE parse error
   3.420 +      var err = responseXML.parseError;
   3.421 +      var msg = 'Parse error in line ' + err.line + ', col ' + err.linePos + ' (error code: ' + err.errorCode + ")\n" +
   3.422 +        "\nError Reason: " + err.reason +
   3.423 +        'Error Line: ' + err.srcText;
   3.424 +
   3.425 +      geoXML3.log('Unable to retrieve ' + doc.url + ': ' + msg);
   3.426 +      if (parserOptions.failedParse) parserOptions.failedParse(doc);
   3.427 +      doc.failed = true;
   3.428 +      return;
   3.429 +    } else if (responseXML.documentElement && responseXML.documentElement.nodeName == 'parsererror') {
   3.430 +      // Firefox parse error
   3.431 +      geoXML3.log('Unable to retrieve ' + doc.url + ': ' + responseXML.documentElement.childNodes[0].nodeValue);
   3.432 +      if (parserOptions.failedParse) parserOptions.failedParse(doc);
   3.433 +      doc.failed = true;
   3.434 +      return;
   3.435 +    } else if (!doc) {
   3.436 +      throw 'geoXML3 internal error: render called with null document';
   3.437 +    } else { //no errors
   3.438 +      var i;
   3.439 +      doc.placemarks      = [];
   3.440 +      doc.groundoverlays  = [];
   3.441 +      doc.ggroundoverlays = [];
   3.442 +      doc.networkLinks    = [];
   3.443 +      doc.gpolygons       = [];
   3.444 +      doc.gpolylines      = [];
   3.445 +
   3.446 +      // Check for dependent KML files
   3.447 +      var nodes = getElementsByTagName(responseXML, 'styleUrl');
   3.448 +      var docSet = doc.internals.docSet;
   3.449 +
   3.450 +      for (var i = 0; i < nodes.length; i++) {
   3.451 +        var url = nodeValue(nodes[i]).split('#')[0];
   3.452 +        if (!url)                 continue;  // #id (inside doc)
   3.453 +        var rUrl = cleanURL( doc.baseDir, url );
   3.454 +        if (rUrl === doc.baseUrl) continue;  // self
   3.455 +        if (docsByUrl[rUrl])      continue;  // already loaded
   3.456 +
   3.457 +        var thisDoc;
   3.458 +        var j = docSet.indexOfObjWithItem('baseUrl', rUrl);
   3.459 +        if (j != -1) {
   3.460 +          // Already listed to be loaded, but probably in the wrong order.
   3.461 +          // Load it right away to immediately resolve dependency.
   3.462 +          thisDoc = docSet[j];
   3.463 +          if (thisDoc.failed) continue;  // failed to load last time; don't retry it again
   3.464 +        }
   3.465 +        else {
   3.466 +          // Not listed at all; add it in
   3.467 +          thisDoc           = new Object();
   3.468 +          thisDoc.url       = rUrl;  // url can't be trusted inside KMZ files, since it may .. outside of the archive
   3.469 +          thisDoc.baseUrl   = rUrl;
   3.470 +          thisDoc.internals = doc.internals;
   3.471 +
   3.472 +          doc.internals.docSet.push(thisDoc);
   3.473 +          doc.internals.remaining++;
   3.474 +        }
   3.475 +
   3.476 +        // render dependent KML first then re-run renderer
   3.477 +        fetchDoc(rUrl, thisDoc, function (thisResXML) {
   3.478 +          render(thisResXML, thisDoc);
   3.479 +          render(responseXML, doc);
   3.480 +        });
   3.481 +
   3.482 +        // to prevent cross-dependency issues, just load the one
   3.483 +        // file first and re-check the rest later
   3.484 +        return;
   3.485 +      }
   3.486 +
   3.487 +      // Parse styles
   3.488 +      doc.styles = styles[doc.baseUrl] = styles[doc.baseUrl] || {};
   3.489 +      var styleID, styleNodes;
   3.490 +      nodes = getElementsByTagName(responseXML, 'Style');
   3.491 +      nodeCount = nodes.length;
   3.492 +      for (i = 0; i < nodeCount; i++) {
   3.493 +        thisNode = nodes[i];
   3.494 +        var styleID = thisNode.getAttribute('id');
   3.495 +        if (!!styleID) processStyle(thisNode, doc.baseUrl, styleID, doc.baseDir);
   3.496 +      }
   3.497 +      // Parse StyleMap nodes
   3.498 +      nodes = getElementsByTagName(responseXML, 'StyleMap');
   3.499 +      for (i = 0; i < nodes.length; i++) {
   3.500 +        thisNode = nodes[i];
   3.501 +        var styleID = thisNode.getAttribute('id');
   3.502 +        if (!!styleID) processStyleMap(thisNode, doc.baseUrl, styleID, doc.baseDir);
   3.503 +      }
   3.504 +
   3.505 +      if (!!parserOptions.processStyles || !parserOptions.createMarker) {
   3.506 +        // Convert parsed styles into GMaps equivalents
   3.507 +        processStyles(doc);
   3.508 +      }
   3.509 +
   3.510 +      // Parse placemarks
   3.511 +      if (!!doc.reload && !!doc.markers) {
   3.512 +        for (i = 0; i < doc.markers.length; i++) {
   3.513 +          doc.markers[i].active = false;
   3.514 +        }
   3.515 +      }
   3.516 +      var placemark, node, coords, path, marker, poly;
   3.517 +      var placemark, coords, path, pathLength, marker, polygonNodes, coordList;
   3.518 +      var placemarkNodes = getElementsByTagName(responseXML, 'Placemark');
   3.519 +      for (pm = 0; pm < placemarkNodes.length; pm++) {
   3.520 +        // Init the placemark object
   3.521 +        node = placemarkNodes[pm];
   3.522 +        var styleUrl = processStyleUrl(node);
   3.523 +        placemark = {
   3.524 +          name:         nodeValue(getElementsByTagName(node, 'name')[0]),
   3.525 +          description:  nodeValue(getElementsByTagName(node, 'description')[0]),
   3.526 +          styleUrl:     styleUrl.join('#'),
   3.527 +          styleBaseUrl: styleUrl[0] ? cleanURL(doc.baseDir, styleUrl[0]) : doc.baseUrl,
   3.528 +          styleID:      styleUrl[1],
   3.529 +          visibility:        getBooleanValue(getElementsByTagName(node, 'visibility')[0], true),
   3.530 +          balloonVisibility: getBooleanValue(getElementsByTagNameNS(node, gxNS, 'balloonVisibility')[0], !parserOptions.suppressInfoWindows)
   3.531 +        };
   3.532 +        placemark.style = (styles[placemark.styleBaseUrl] && styles[placemark.styleBaseUrl][placemark.styleID]) || clone(defaultStyle);
   3.533 +        // inline style overrides shared style
   3.534 +        var inlineStyles = getElementsByTagName(node, 'Style');
   3.535 +        if (inlineStyles && (inlineStyles.length > 0)) {
   3.536 +          var style = processStyle(node, '{inline}', '{inline}');
   3.537 +          processStyleID(style);
   3.538 +          if (style) placemark.style = style;
   3.539 +        }
   3.540 +
   3.541 +        if (/^https?:\/\//.test(placemark.description)) {
   3.542 +          placemark.description = ['<a href="', placemark.description, '">', placemark.description, '</a>'].join('');
   3.543 +        }
   3.544 +
   3.545 +        // record list of variables for substitution
   3.546 +        placemark.vars = {
   3.547 +          display: {
   3.548 +            name:         'Name',
   3.549 +            description:  'Description',
   3.550 +            address:      'Street Address',
   3.551 +            id:           'ID',
   3.552 +            Snippet:      'Snippet',
   3.553 +            geDirections: 'Directions'
   3.554 +          },
   3.555 +          val: {
   3.556 +            name:        placemark.name || '',
   3.557 +            description: placemark.description || '',
   3.558 +            address:     nodeValue(getElementsByTagName(node, 'address')[0], ''),
   3.559 +            id:          node.getAttribute('id') || '',
   3.560 +            Snippet:     nodeValue(getElementsByTagName(node, 'Snippet')[0], '')
   3.561 +          },
   3.562 +          directions: [
   3.563 +            'f=d',
   3.564 +            'source=GeoXML3'
   3.565 +          ]
   3.566 +        };
   3.567 +
   3.568 +        // add extended data to variables
   3.569 +        var extDataNodes = getElementsByTagName(node, 'ExtendedData');
   3.570 +        if (!!extDataNodes && extDataNodes.length > 0) {
   3.571 +          var dataNodes = getElementsByTagName(extDataNodes[0], 'Data');
   3.572 +          for (var d = 0; d < dataNodes.length; d++) {
   3.573 +            var dn    = dataNodes[d];
   3.574 +            var name  = dn.getAttribute('name');
   3.575 +            if (!name) continue;
   3.576 +            var dName = nodeValue(getElementsByTagName(dn, 'displayName')[0], name);
   3.577 +            var val   = nodeValue(getElementsByTagName(dn, 'value')[0]);
   3.578 +
   3.579 +            placemark.vars.val[name]     = val;
   3.580 +            placemark.vars.display[name] = dName;
   3.581 +          }
   3.582 +        }
   3.583 +
   3.584 +        // process MultiGeometry
   3.585 +        var GeometryNodes = getElementsByTagName(node, 'coordinates');
   3.586 +        var Geometry = null;
   3.587 +        if (!!GeometryNodes && (GeometryNodes.length > 0)) {
   3.588 +          for (var gn=0;gn<GeometryNodes.length;gn++) {
   3.589 +            if (GeometryNodes[gn].parentNode &&
   3.590 +                GeometryNodes[gn].parentNode.nodeName) {
   3.591 +              var GeometryPN = GeometryNodes[gn].parentNode;
   3.592 +              Geometry = GeometryPN.nodeName;
   3.593 +
   3.594 +              // Extract the coordinates
   3.595 +              // What sort of placemark?
   3.596 +              switch(Geometry) {
   3.597 +                case "Point":
   3.598 +                  placemark.Point = processPlacemarkCoords(node, "Point")[0];
   3.599 +                  placemark.latlng = new google.maps.LatLng(placemark.Point.coordinates[0].lat, placemark.Point.coordinates[0].lng);
   3.600 +                  pathLength = 1;
   3.601 +                  break;
   3.602 +                case "LinearRing":
   3.603 +                  // Polygon/line
   3.604 +                  polygonNodes = getElementsByTagName(node, 'Polygon');
   3.605 +                  // Polygon
   3.606 +                  if (!placemark.Polygon)
   3.607 +                    placemark.Polygon = [{
   3.608 +                      outerBoundaryIs: {coordinates: []},
   3.609 +                      innerBoundaryIs: [{coordinates: []}]
   3.610 +                    }];
   3.611 +                  for (var pg=0;pg<polygonNodes.length;pg++) {
   3.612 +                     placemark.Polygon[pg] = {
   3.613 +                       outerBoundaryIs: {coordinates: []},
   3.614 +                       innerBoundaryIs: [{coordinates: []}]
   3.615 +                     }
   3.616 +                     placemark.Polygon[pg].outerBoundaryIs = processPlacemarkCoords(polygonNodes[pg], "outerBoundaryIs");
   3.617 +                     placemark.Polygon[pg].innerBoundaryIs = processPlacemarkCoords(polygonNodes[pg], "innerBoundaryIs");
   3.618 +                  }
   3.619 +                  coordList = placemark.Polygon[0].outerBoundaryIs;
   3.620 +                  break;
   3.621 +
   3.622 +                case "LineString":
   3.623 +                  pathLength = 0;
   3.624 +                  placemark.LineString = processPlacemarkCoords(node,"LineString");
   3.625 +                  break;
   3.626 +
   3.627 +                default:
   3.628 +                  break;
   3.629 +              }
   3.630 +            }
   3.631 +          }
   3.632 +        }
   3.633 +
   3.634 +        // call the custom placemark parse function if it is defined
   3.635 +        if (!!parserOptions.pmParseFn) parserOptions.pmParseFn(node, placemark);
   3.636 +        doc.placemarks.push(placemark);
   3.637 +
   3.638 +        // single marker
   3.639 +        if (placemark.Point) {
   3.640 +          if (!!google.maps) {
   3.641 +            doc.bounds = doc.bounds || new google.maps.LatLngBounds();
   3.642 +            doc.bounds.extend(placemark.latlng);
   3.643 +          }
   3.644 +
   3.645 +          // Potential user-defined marker handler
   3.646 +          var pointCreateFunc = parserOptions.createMarker || createMarker;
   3.647 +          var found = false;
   3.648 +          if (!parserOptions.createMarker) {
   3.649 +            // Check to see if this marker was created on a previous load of this document
   3.650 +            if (!!doc) {
   3.651 +              doc.markers = doc.markers || [];
   3.652 +              if (doc.reload) {
   3.653 +                for (var j = 0; j < doc.markers.length; j++) {
   3.654 +                  if (doc.markers[j].getPosition().equals(placemark.latlng)) {
   3.655 +                    found = doc.markers[j].active = true;
   3.656 +                    break;
   3.657 +                  }
   3.658 +                }
   3.659 +              }
   3.660 +            }
   3.661 +          }
   3.662 +          if (!found) {
   3.663 +            // Call the marker creator
   3.664 +            var marker = pointCreateFunc(placemark, doc);
   3.665 +            if (marker) marker.active = placemark.visibility;
   3.666 +          }
   3.667 +        }
   3.668 +        // polygon/line
   3.669 +        var poly, line;
   3.670 +        if (!!doc) {
   3.671 +          if (placemark.Polygon)    doc.gpolygons  = doc.gpolygons  || [];
   3.672 +          if (placemark.LineString) doc.gpolylines = doc.gpolylines || [];
   3.673 +        }
   3.674 +
   3.675 +        var polyCreateFunc = parserOptions.createPolygon    || createPolygon;
   3.676 +        var lineCreateFunc = parserOptions.createLineString || createPolyline;
   3.677 +        if (placemark.Polygon) {
   3.678 +          poly = polyCreateFunc(placemark,doc);
   3.679 +          if (poly) poly.active = placemark.visibility;
   3.680 +        }
   3.681 +        if (placemark.LineString) {
   3.682 +          line = lineCreateFunc(placemark,doc);
   3.683 +          if (line) line.active = placemark.visibility;
   3.684 +        }
   3.685 +        if (!!google.maps) {
   3.686 +          doc.bounds = doc.bounds || new google.maps.LatLngBounds();
   3.687 +          if (poly) doc.bounds.union(poly.bounds);
   3.688 +          if (line) doc.bounds.union(line.bounds);
   3.689 +        }
   3.690 +
   3.691 +      } // placemark loop
   3.692 +
   3.693 +      if (!!doc.reload && !!doc.markers) {
   3.694 +        for (i = doc.markers.length - 1; i >= 0 ; i--) {
   3.695 +          if (!doc.markers[i].active) {
   3.696 +            if (!!doc.markers[i].infoWindow) {
   3.697 +              doc.markers[i].infoWindow.close();
   3.698 +            }
   3.699 +            doc.markers[i].setMap(null);
   3.700 +            doc.markers.splice(i, 1);
   3.701 +          }
   3.702 +        }
   3.703 +      }
   3.704 +
   3.705 +      // Parse ground overlays
   3.706 +      if (!!doc.reload && !!doc.groundoverlays) {
   3.707 +        for (i = 0; i < doc.groundoverlays.length; i++) {
   3.708 +          doc.groundoverlays[i].active = false;
   3.709 +        }
   3.710 +      }
   3.711 +
   3.712 +      if (!!doc) {
   3.713 +        doc.groundoverlays = doc.groundoverlays || [];
   3.714 +      }
   3.715 +      // doc.groundoverlays =[];
   3.716 +      var groundOverlay, color, transparency, overlay;
   3.717 +      var groundNodes = getElementsByTagName(responseXML, 'GroundOverlay');
   3.718 +      for (i = 0; i < groundNodes.length; i++) {
   3.719 +        node = groundNodes[i];
   3.720 +
   3.721 +        // Detect images buried in KMZ files (and use a base64 encoded URL)
   3.722 +        var gnUrl = cleanURL( doc.baseDir, nodeValue(getElementsByTagName(node, 'href')[0]) );
   3.723 +        if (kmzMetaData[gnUrl]) gnUrl = kmzMetaData[gnUrl].dataUrl;
   3.724 +
   3.725 +        // Init the ground overlay object
   3.726 +        groundOverlay = {
   3.727 +          name:        nodeValue(getElementsByTagName(node, 'name')[0]),
   3.728 +          description: nodeValue(getElementsByTagName(node, 'description')[0]),
   3.729 +          icon: { href: gnUrl },
   3.730 +          latLonBox: {
   3.731 +            north: parseFloat(nodeValue(getElementsByTagName(node, 'north')[0])),
   3.732 +            east:  parseFloat(nodeValue(getElementsByTagName(node, 'east')[0])),
   3.733 +            south: parseFloat(nodeValue(getElementsByTagName(node, 'south')[0])),
   3.734 +            west:  parseFloat(nodeValue(getElementsByTagName(node, 'west')[0]))
   3.735 +          }
   3.736 +        };
   3.737 +        if (!!google.maps) {
   3.738 +          doc.bounds = doc.bounds || new google.maps.LatLngBounds();
   3.739 +          doc.bounds.union(new google.maps.LatLngBounds(
   3.740 +            new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west),
   3.741 +            new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east)
   3.742 +          ));
   3.743 +        }
   3.744 +
   3.745 +        // Opacity is encoded in the color node
   3.746 +        var colorNode = getElementsByTagName(node, 'color');
   3.747 +        if (colorNode && colorNode.length > 0) {
   3.748 +          groundOverlay.opacity = geoXML3.getOpacity(nodeValue(colorNode[0]));
   3.749 +        } else {
   3.750 +          groundOverlay.opacity = 1.0;  // KML default
   3.751 +        }
   3.752 +
   3.753 +        doc.groundoverlays.push(groundOverlay);
   3.754 +        if (!!parserOptions.createOverlay) {
   3.755 +          // User-defined overlay handler
   3.756 +          parserOptions.createOverlay(groundOverlay, doc);
   3.757 +        } else {
   3.758 +          // Check to see if this overlay was created on a previous load of this document
   3.759 +          var found = false;
   3.760 +          if (!!doc) {
   3.761 +            doc.groundoverlays = doc.groundoverlays || [];
   3.762 +            if (doc.reload) {
   3.763 +              overlayBounds = new google.maps.LatLngBounds(
   3.764 +                new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west),
   3.765 +                new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east)
   3.766 +              );
   3.767 +              var overlays = doc.groundoverlays;
   3.768 +              for (i = overlays.length; i--;) {
   3.769 +                if ((overlays[i].bounds().equals(overlayBounds)) &&
   3.770 +                    (overlays.url_ === groundOverlay.icon.href)) {
   3.771 +                  found = overlays[i].active = true;
   3.772 +                  break;
   3.773 +                }
   3.774 +              }
   3.775 +            }
   3.776 +          }
   3.777 +
   3.778 +          if (!found) {
   3.779 +            // Call the built-in overlay creator
   3.780 +            overlay = createOverlay(groundOverlay, doc);
   3.781 +            overlay.active = true;
   3.782 +          }
   3.783 +        }
   3.784 +        if (!!doc.reload && !!doc.groundoverlays && !!doc.groundoverlays.length) {
   3.785 +          var overlays = doc.groundoverlays;
   3.786 +          for (i = overlays.length; i--;) {
   3.787 +            if (!overlays[i].active) {
   3.788 +              overlays[i].remove();
   3.789 +              overlays.splice(i, 1);
   3.790 +            }
   3.791 +          }
   3.792 +          doc.groundoverlays = overlays;
   3.793 +        }
   3.794 +      }
   3.795 +
   3.796 +      // Parse network links
   3.797 +      var networkLink;
   3.798 +      var docPath = document.location.pathname.split('/');
   3.799 +      docPath = docPath.splice(0, docPath.length - 1).join('/');
   3.800 +      var linkNodes = getElementsByTagName(responseXML, 'NetworkLink');
   3.801 +      for (i = 0; i < linkNodes.length; i++) {
   3.802 +        node = linkNodes[i];
   3.803 +
   3.804 +        // Init the network link object
   3.805 +        networkLink = {
   3.806 +          name: nodeValue(getElementsByTagName(node, 'name')[0]),
   3.807 +          link: {
   3.808 +            href:        nodeValue(getElementsByTagName(node, 'href')[0]),
   3.809 +            refreshMode: nodeValue(getElementsByTagName(node, 'refreshMode')[0])
   3.810 +          }
   3.811 +        };
   3.812 +
   3.813 +        // Establish the specific refresh mode
   3.814 +        if (!networkLink.link.refreshMode) {
   3.815 +          networkLink.link.refreshMode = 'onChange';
   3.816 +        }
   3.817 +        if (networkLink.link.refreshMode === 'onInterval') {
   3.818 +          networkLink.link.refreshInterval = parseFloat(nodeValue(getElementsByTagName(node, 'refreshInterval')[0]));
   3.819 +          if (isNaN(networkLink.link.refreshInterval)) {
   3.820 +            networkLink.link.refreshInterval = 0;
   3.821 +          }
   3.822 +        } else if (networkLink.link.refreshMode === 'onChange') {
   3.823 +          networkLink.link.viewRefreshMode = nodeValue(getElementsByTagName(node, 'viewRefreshMode')[0]);
   3.824 +          if (!networkLink.link.viewRefreshMode) {
   3.825 +            networkLink.link.viewRefreshMode = 'never';
   3.826 +          }
   3.827 +          if (networkLink.link.viewRefreshMode === 'onStop') {
   3.828 +            networkLink.link.viewRefreshTime = nodeValue(getElementsByTagName(node, 'refreshMode')[0]);
   3.829 +            networkLink.link.viewFormat =      nodeValue(getElementsByTagName(node, 'refreshMode')[0]);
   3.830 +            if (!networkLink.link.viewFormat) {
   3.831 +              networkLink.link.viewFormat = 'BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth]';
   3.832 +            }
   3.833 +          }
   3.834 +        }
   3.835 +
   3.836 +        if (!/^[\/|http]/.test(networkLink.link.href)) {
   3.837 +          // Fully-qualify the HREF
   3.838 +          networkLink.link.href = docPath + '/' + networkLink.link.href;
   3.839 +        }
   3.840 +
   3.841 +        // Apply the link
   3.842 +        if ((networkLink.link.refreshMode === 'onInterval') &&
   3.843 +            (networkLink.link.refreshInterval > 0)) {
   3.844 +          // Reload at regular intervals
   3.845 +          setInterval(parserName + '.parse("' + networkLink.link.href + '")',
   3.846 +                      1000 * networkLink.link.refreshInterval);
   3.847 +        } else if (networkLink.link.refreshMode === 'onChange') {
   3.848 +          if (networkLink.link.viewRefreshMode === 'never') {
   3.849 +            // Load the link just once
   3.850 +            doc.internals.parser.parse(networkLink.link.href, doc.internals.docSet);
   3.851 +          } else if (networkLink.link.viewRefreshMode === 'onStop') {
   3.852 +            // Reload when the map view changes
   3.853 +
   3.854 +          }
   3.855 +        }
   3.856 +      }
   3.857 +    }
   3.858 +
   3.859 +    if (!!doc.bounds) {
   3.860 +      doc.internals.bounds = doc.internals.bounds || new google.maps.LatLngBounds();
   3.861 +      doc.internals.bounds.union(doc.bounds);
   3.862 +    }
   3.863 +    if (!!doc.markers || !!doc.groundoverlays || !!doc.gpolylines || !!doc.gpolygons) {
   3.864 +      doc.internals.parseOnly = false;
   3.865 +    }
   3.866 +
   3.867 +    if (!doc.internals.parseOnly) {
   3.868 +      // geoXML3 is not being used only as a real-time parser, so keep the processed documents around
   3.869 +      if (!docsByUrl[doc.baseUrl]) {
   3.870 +        docs.push(doc);
   3.871 +        docsByUrl[doc.baseUrl] = doc;
   3.872 +      }
   3.873 +      else {
   3.874 +        // internal replacement, which keeps the same memory ref loc in docs and docsByUrl
   3.875 +        for (var i in docsByUrl[doc.baseUrl]) {
   3.876 +          docsByUrl[doc.baseUrl][i] = doc[i];
   3.877 +        }
   3.878 +      }
   3.879 +    }
   3.880 +
   3.881 +    doc.internals.remaining--;
   3.882 +    if (doc.internals.remaining === 0) {
   3.883 +      // We're done processing this set of KML documents
   3.884 +      // Options that get invoked after parsing completes
   3.885 +      if (parserOptions.zoom && !!doc.internals.bounds &&
   3.886 +	  !doc.internals.bounds.isEmpty() && !!parserOptions.map) {
   3.887 +        parserOptions.map.fitBounds(doc.internals.bounds);
   3.888 +      }
   3.889 +      if (parserOptions.afterParse) {
   3.890 +        parserOptions.afterParse(doc.internals.docSet);
   3.891 +      }
   3.892 +    }
   3.893 +  };
   3.894 +
   3.895 +  var kmlColor = function (kmlIn, colorMode) {
   3.896 +    var kmlColor = {};
   3.897 +    kmlIn = kmlIn || 'ffffffff';  // white (KML 2.2 default)
   3.898 +
   3.899 +    var aa = kmlIn.substr(0,2);
   3.900 +    var bb = kmlIn.substr(2,2);
   3.901 +    var gg = kmlIn.substr(4,2);
   3.902 +    var rr = kmlIn.substr(6,2);
   3.903 +
   3.904 +    kmlColor.opacity = parseInt(aa, 16) / 256;
   3.905 +    kmlColor.color   = (colorMode === 'random') ? randomColor(rr, gg, bb) : '#' + rr + gg + bb;
   3.906 +    return kmlColor;
   3.907 +  };
   3.908 +
   3.909 +  // Implemented per KML 2.2 <ColorStyle> specs
   3.910 +  var randomColor = function(rr, gg, bb) {
   3.911 +    var col = { rr: rr, gg: gg, bb: bb };
   3.912 +    for (var k in col) {
   3.913 +      var v = col[k];
   3.914 +      if (v == null) v = 'ff';
   3.915 +
   3.916 +      // RGB values are limiters for random numbers (ie: 7f would be a random value between 0 and 7f)
   3.917 +      v = Math.round(Math.random() * parseInt(rr, 16)).toString(16);
   3.918 +      if (v.length === 1) v = '0' + v;
   3.919 +      col[k] = v;
   3.920 +    }
   3.921 +
   3.922 +    return '#' + col.rr + col.gg + col.bb;
   3.923 +  };
   3.924 +
   3.925 +  var processStyleID = function (style) {
   3.926 +    var icon = style.icon;
   3.927 +    if (!icon.href) return;
   3.928 +
   3.929 +    if (icon.img && !icon.img.complete && (icon.dim.w < 0) && (icon.dim.h < 0) ) {
   3.930 +      // we're still waiting on the image loading (probably because we've been blocking since the declaration)
   3.931 +      // so, let's queue this function on the onload stack
   3.932 +      icon.markerBacklog = [];
   3.933 +      icon.img.onload = function() {
   3.934 +        if (icon.dim.w < 0 || icon.dim.h < 0) {
   3.935 +          icon.dim.w = this.width;
   3.936 +          icon.dim.h = this.height;
   3.937 +        }
   3.938 +        processStyleID(style);
   3.939 +
   3.940 +        // we will undoubtedly get some createMarker queuing, so set this up in advance
   3.941 +        for (var i = 0; i < icon.markerBacklog.length; i++) {
   3.942 +          var p = icon.markerBacklog[i][0];
   3.943 +          var d = icon.markerBacklog[i][1];
   3.944 +          createMarker(p, d);
   3.945 +          if (p.marker) p.marker.active = true;
   3.946 +        }
   3.947 +        delete icon.markerBacklog;
   3.948 +      };
   3.949 +      return;
   3.950 +    }
   3.951 +    else if (icon.dim.w < 0 || icon.dim.h < 0) {
   3.952 +      if (icon.img && icon.img.complete) {
   3.953 +        // sometimes the file is already cached and it never calls onLoad
   3.954 +        icon.dim.w = icon.img.width;
   3.955 +        icon.dim.h = icon.img.height;
   3.956 +      }
   3.957 +      else {
   3.958 +        // settle for a default of 32x32
   3.959 +        icon.dim.whGuess = true;
   3.960 +        icon.dim.w = 32;
   3.961 +        icon.dim.h = 32;
   3.962 +      }
   3.963 +    }
   3.964 +
   3.965 +    // pre-scaled variables
   3.966 +    var rnd = Math.round;
   3.967 +    var scaled = {
   3.968 +      x:  icon.dim.x     * icon.scale,
   3.969 +      y:  icon.dim.y     * icon.scale,
   3.970 +      w:  icon.dim.w     * icon.scale,
   3.971 +      h:  icon.dim.h     * icon.scale,
   3.972 +      aX: icon.hotSpot.x * icon.scale,
   3.973 +      aY: icon.hotSpot.y * icon.scale,
   3.974 +      iW: (icon.img ? icon.img.width  : icon.dim.w) * icon.scale,
   3.975 +      iH: (icon.img ? icon.img.height : icon.dim.h) * icon.scale
   3.976 +    };
   3.977 +
   3.978 +    // Figure out the anchor spot
   3.979 +    var aX, aY;
   3.980 +    switch (icon.hotSpot.xunits) {
   3.981 +      case 'fraction':    aX = rnd(scaled.aX * icon.dim.w); break;
   3.982 +      case 'insetPixels': aX = rnd(icon.dim.w * icon.scale - scaled.aX); break;
   3.983 +      default:            aX = rnd(scaled.aX); break;  // already pixels
   3.984 +    }
   3.985 +    aY = rnd( ((icon.hotSpot.yunits === 'fraction') ? icon.dim.h : 1) * scaled.aY );  // insetPixels Y = pixels Y
   3.986 +    var iconAnchor = new google.maps.Point(aX, aY);
   3.987 +
   3.988 +    // Sizes
   3.989 +    // (NOTE: Scale is applied to entire image, not just the section of the icon palette.)
   3.990 +    var iconSize   = icon.dim.whGuess  ? null : new google.maps.Size(rnd(scaled.w),  rnd(scaled.h));
   3.991 +    var iconScale  = icon.scale == 1.0 ? null :
   3.992 +                     icon.dim.whGuess  ?        new google.maps.Size(rnd(scaled.w),  rnd(scaled.h))
   3.993 +                                              : new google.maps.Size(rnd(scaled.iW), rnd(scaled.iH));
   3.994 +    var iconOrigin = new google.maps.Point(rnd(scaled.x), rnd(scaled.y));
   3.995 +
   3.996 +    // Detect images buried in KMZ files (and use a base64 encoded URL)
   3.997 +    if (kmzMetaData[icon.url]) icon.url = kmzMetaData[icon.url].dataUrl;
   3.998 +
   3.999 +    // Init the style object with the KML icon
  3.1000 +    icon.marker = new google.maps.MarkerImage(
  3.1001 +      icon.url,    // url
  3.1002 +      iconSize,    // size
  3.1003 +      iconOrigin,  // origin
  3.1004 +      iconAnchor,  // anchor
  3.1005 +      iconScale    // scaledSize
  3.1006 +    );
  3.1007 +
  3.1008 +    // Look for a predictable shadow
  3.1009 +    var stdRegEx = /\/(red|blue|green|yellow|lightblue|purple|pink|orange)(-dot)?\.png/;
  3.1010 +    var shadowSize = new google.maps.Size(59, 32);
  3.1011 +    var shadowPoint = new google.maps.Point(16, 32);
  3.1012 +    if (stdRegEx.test(icon.href)) {
  3.1013 +      // A standard GMap-style marker icon
  3.1014 +      icon.shadow = new google.maps.MarkerImage(
  3.1015 +        'http://maps.google.com/mapfiles/ms/micons/msmarker.shadow.png', // url
  3.1016 +        shadowSize,                                                      // size
  3.1017 +        null,                                                            // origin
  3.1018 +        shadowPoint,                                                     // anchor
  3.1019 +        shadowSize                                                       // scaledSize
  3.1020 +      );
  3.1021 +    } else if (icon.href.indexOf('-pushpin.png') > -1) {
  3.1022 +      // Pushpin marker icon
  3.1023 +      icon.shadow = new google.maps.MarkerImage(
  3.1024 +        'http://maps.google.com/mapfiles/ms/micons/pushpin_shadow.png',  // url
  3.1025 +        shadowSize,                                                      // size
  3.1026 +        null,                                                            // origin
  3.1027 +        shadowPoint,                                                     // anchor
  3.1028 +        shadowSize                                                       // scaledSize
  3.1029 +      );
  3.1030 +    } /* else {
  3.1031 +      // Other MyMaps KML standard icon
  3.1032 +      icon.shadow = new google.maps.MarkerImage(
  3.1033 +        icon.href.replace('.png', '.shadow.png'),                        // url
  3.1034 +        shadowSize,                                                      // size
  3.1035 +        null,                                                            // origin
  3.1036 +        anchorPoint,                                                     // anchor
  3.1037 +        shadowSize                                                       // scaledSize
  3.1038 +      );
  3.1039 +    } */
  3.1040 +  }
  3.1041 +
  3.1042 +  var processStyles = function (doc) {
  3.1043 +    for (var styleID in doc.styles) {
  3.1044 +      processStyleID(doc.styles[styleID]);
  3.1045 +    }
  3.1046 +  };
  3.1047 +
  3.1048 +  var createMarker = function (placemark, doc) {
  3.1049 +    // create a Marker to the map from a placemark KML object
  3.1050 +    var icon = placemark.style.icon;
  3.1051 +
  3.1052 +    if ( !icon.marker && icon.img ) {
  3.1053 +      // yay, single point of failure is holding up multiple markers...
  3.1054 +      icon.markerBacklog = icon.markerBacklog || [];
  3.1055 +      icon.markerBacklog.push([placemark, doc]);
  3.1056 +      return;
  3.1057 +    }
  3.1058 +
  3.1059 +    // Load basic marker properties
  3.1060 +    var markerOptions = geoXML3.combineOptions(parserOptions.markerOptions, {
  3.1061 +      map:      parserOptions.map,
  3.1062 +      position: new google.maps.LatLng(placemark.Point.coordinates[0].lat, placemark.Point.coordinates[0].lng),
  3.1063 +      title:    placemark.name,
  3.1064 +      zIndex:   Math.round(placemark.Point.coordinates[0].lat * -100000)<<5,
  3.1065 +      icon:     icon.marker,
  3.1066 +      shadow:   icon.shadow,
  3.1067 +      flat:     !icon.shadow,
  3.1068 +      visible:  placemark.visibility
  3.1069 +    });
  3.1070 +
  3.1071 +    // Create the marker on the map
  3.1072 +    var marker = new google.maps.Marker(markerOptions);
  3.1073 +    if (!!doc) doc.markers.push(marker);
  3.1074 +
  3.1075 +    // Set up and create the infowindow if it is not suppressed
  3.1076 +    createInfoWindow(placemark, doc, marker);
  3.1077 +    placemark.marker = marker;
  3.1078 +    return marker;
  3.1079 +  };
  3.1080 +
  3.1081 +  var createOverlay = function (groundOverlay, doc) {
  3.1082 +    // Add a ProjectedOverlay to the map from a groundOverlay KML object
  3.1083 +
  3.1084 +    if (!window.ProjectedOverlay) {
  3.1085 +      throw 'geoXML3 error: ProjectedOverlay not found while rendering GroundOverlay from KML';
  3.1086 +    }
  3.1087 +
  3.1088 +    var bounds = new google.maps.LatLngBounds(
  3.1089 +        new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west),
  3.1090 +        new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east)
  3.1091 +    );
  3.1092 +    var overlayOptions = geoXML3.combineOptions(parserOptions.overlayOptions, {percentOpacity: groundOverlay.opacity*100});
  3.1093 +    var overlay = new ProjectedOverlay(parserOptions.map, groundOverlay.icon.href, bounds, overlayOptions);
  3.1094 +
  3.1095 +    if (!!doc) {
  3.1096 +      doc.ggroundoverlays = doc.ggroundoverlays || [];
  3.1097 +      doc.ggroundoverlays.push(overlay);
  3.1098 +    }
  3.1099 +
  3.1100 +    return overlay;
  3.1101 +  };
  3.1102 +
  3.1103 +  // Create Polyline
  3.1104 +  var createPolyline = function(placemark, doc) {
  3.1105 +    var path = [];
  3.1106 +    for (var j=0; j<placemark.LineString.length; j++) {
  3.1107 +      var coords = placemark.LineString[j].coordinates;
  3.1108 +      var bounds = new google.maps.LatLngBounds();
  3.1109 +      for (var i=0;i<coords.length;i++) {
  3.1110 +        var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng);
  3.1111 +        path.push(pt);
  3.1112 +        bounds.extend(pt);
  3.1113 +      }
  3.1114 +    }
  3.1115 +    // point to open the infowindow if triggered
  3.1116 +    var point = path[Math.floor(path.length/2)];
  3.1117 +    // Load basic polyline properties
  3.1118 +    var kmlStrokeColor = kmlColor(placemark.style.line.color, placemark.style.line.colorMode);
  3.1119 +    var polyOptions = geoXML3.combineOptions(parserOptions.polylineOptions, {
  3.1120 +      map:           parserOptions.map,
  3.1121 +      path:          path,
  3.1122 +      strokeColor:   kmlStrokeColor.color,
  3.1123 +      strokeWeight:  placemark.style.line.width,
  3.1124 +      strokeOpacity: kmlStrokeColor.opacity,
  3.1125 +      title:         placemark.name,
  3.1126 +      visible:       placemark.visibility
  3.1127 +    });
  3.1128 +    var p = new google.maps.Polyline(polyOptions);
  3.1129 +    p.bounds = bounds;
  3.1130 +
  3.1131 +    // setup and create the infoWindow if it is not suppressed
  3.1132 +    createInfoWindow(placemark, doc, p);
  3.1133 +    if (!!doc) doc.gpolylines.push(p);
  3.1134 +    placemark.polyline = p;
  3.1135 +    return p;
  3.1136 +  }
  3.1137 +
  3.1138 +  // Create Polygon
  3.1139 +  var createPolygon = function(placemark, doc) {
  3.1140 +    var bounds = new google.maps.LatLngBounds();
  3.1141 +    var pathsLength = 0;
  3.1142 +    var paths = [];
  3.1143 +    for (var polygonPart=0;polygonPart<placemark.Polygon.length;polygonPart++) {
  3.1144 +      for (var j=0; j<placemark.Polygon[polygonPart].outerBoundaryIs.length; j++) {
  3.1145 +        var coords = placemark.Polygon[polygonPart].outerBoundaryIs[j].coordinates;
  3.1146 +        var path = [];
  3.1147 +        for (var i=0;i<coords.length;i++) {
  3.1148 +          var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng);
  3.1149 +          path.push(pt);
  3.1150 +          bounds.extend(pt);
  3.1151 +        }
  3.1152 +        paths.push(path);
  3.1153 +        pathsLength += path.length;
  3.1154 +      }
  3.1155 +      for (var j=0; j<placemark.Polygon[polygonPart].innerBoundaryIs.length; j++) {
  3.1156 +        var coords = placemark.Polygon[polygonPart].innerBoundaryIs[j].coordinates;
  3.1157 +        var path = [];
  3.1158 +        for (var i=0;i<coords.length;i++) {
  3.1159 +          var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng);
  3.1160 +          path.push(pt);
  3.1161 +          bounds.extend(pt);
  3.1162 +        }
  3.1163 +        paths.push(path);
  3.1164 +        pathsLength += path.length;
  3.1165 +      }
  3.1166 +    }
  3.1167 +
  3.1168 +    // Load basic polygon properties
  3.1169 +    var kmlStrokeColor = kmlColor(placemark.style.line.color, placemark.style.line.colorMode);
  3.1170 +    var kmlFillColor = kmlColor(placemark.style.poly.color, placemark.style.poly.colorMode);
  3.1171 +    if (!placemark.style.poly.fill) kmlFillColor.opacity = 0.0;
  3.1172 +    var strokeWeight = placemark.style.line.width;
  3.1173 +    if (!placemark.style.poly.outline) {
  3.1174 +      strokeWeight = 0;
  3.1175 +      kmlStrokeColor.opacity = 0.0;
  3.1176 +    }
  3.1177 +    var polyOptions = geoXML3.combineOptions(parserOptions.polygonOptions, {
  3.1178 +      map:           parserOptions.map,
  3.1179 +      paths:         paths,
  3.1180 +      title:         placemark.name,
  3.1181 +      strokeColor:   kmlStrokeColor.color,
  3.1182 +      strokeWeight:  strokeWeight,
  3.1183 +      strokeOpacity: kmlStrokeColor.opacity,
  3.1184 +      fillColor:     kmlFillColor.color,
  3.1185 +      fillOpacity:   kmlFillColor.opacity,
  3.1186 +      visible:       placemark.visibility
  3.1187 +    });
  3.1188 +    var p = new google.maps.Polygon(polyOptions);
  3.1189 +    p.bounds = bounds;
  3.1190 +
  3.1191 +    createInfoWindow(placemark, doc, p);
  3.1192 +    if (!!doc) doc.gpolygons.push(p);
  3.1193 +    placemark.polygon = p;
  3.1194 +    return p;
  3.1195 +  }
  3.1196 +
  3.1197 +  var createInfoWindow = function(placemark, doc, gObj) {
  3.1198 +    var bStyle = placemark.style.balloon;
  3.1199 +    var vars = placemark.vars;
  3.1200 +
  3.1201 +    if (!placemark.balloonVisibility || bStyle.displayMode === 'hide') return;
  3.1202 +
  3.1203 +    // define geDirections
  3.1204 +    if (placemark.latlng) {
  3.1205 +      vars.directions.push('sll=' + placemark.latlng.toUrlValue());
  3.1206 +
  3.1207 +      var url = 'http://maps.google.com/maps?' + vars.directions.join('&');
  3.1208 +      var address = encodeURIComponent( vars.val.address || placemark.latlng.toUrlValue() ).replace(/\%20/g, '+');
  3.1209 +
  3.1210 +      vars.val.geDirections = '<a href="' + url + '&daddr=' + address + '" target=_blank>To Here</a> - <a href="' + url + '&saddr=' + address + '" target=_blank>From Here</a>';
  3.1211 +    }
  3.1212 +    else vars.val.geDirections = '';
  3.1213 +
  3.1214 +    // add in the variables
  3.1215 +    var iwText = bStyle.text.replace(/\$\[(\w+(\/displayName)?)\]/g, function(txt, n, dn) { return dn ? vars.display[n] : vars.val[n]; });
  3.1216 +    var classTxt = 'geoxml3_infowindow geoxml3_style_' + placemark.styleID;
  3.1217 +
  3.1218 +    // color styles
  3.1219 +    var styleArr = [];
  3.1220 +    if (bStyle.bgColor   != 'ffffffff') styleArr.push('background: ' + kmlColor(bStyle.bgColor  ).color + ';');
  3.1221 +    if (bStyle.textColor != 'ff000000') styleArr.push('color: '      + kmlColor(bStyle.textColor).color + ';');
  3.1222 +    var styleProp = styleArr.length ? ' style="' + styleArr.join(' ') + '"' : '';
  3.1223 +
  3.1224 +    var infoWindowOptions = geoXML3.combineOptions(parserOptions.infoWindowOptions, {
  3.1225 +      content: '<div class="' + classTxt + '"' + styleProp + '>' + iwText + '</div>',
  3.1226 +      pixelOffset: new google.maps.Size(0, 2)
  3.1227 +    });
  3.1228 +
  3.1229 +    gObj.infoWindow = parserOptions.infoWindow || new google.maps.InfoWindow(infoWindowOptions);
  3.1230 +    gObj.infoWindowOptions = infoWindowOptions;
  3.1231 +
  3.1232 +    // Info Window-opening event handler
  3.1233 +    google.maps.event.addListener(gObj, 'click', function(e) {
  3.1234 +      var iW = this.infoWindow;
  3.1235 +      iW.close();
  3.1236 +      iW.setOptions(this.infoWindowOptions);
  3.1237 +
  3.1238 +      if      (e && e.latLng) iW.setPosition(e.latLng);
  3.1239 +      else if (this.bounds)   iW.setPosition(this.bounds.getCenter());
  3.1240 +
  3.1241 +      iW.setContent("<div id='geoxml3_infowindow'>"+iW.getContent()+"</div>");
  3.1242 +      google.maps.event.addListenerOnce(iW, "domready", function() {
  3.1243 +        var node = document.getElementById('geoxml3_infowindow');
  3.1244 +        var imgArray = node.getElementsByTagName('img');
  3.1245 +        for (var i = 0; i < imgArray.length; i++) 
  3.1246 +        {
  3.1247 +          var imgUrlIE = imgArray[i].getAttribute("src");
  3.1248 +          var imgUrl  = cleanURL(doc.baseDir, imgUrlIE);
  3.1249 +
  3.1250 +          if (kmzMetaData[imgUrl]) {
  3.1251 +             imgArray[i].src = kmzMetaData[imgUrl].dataUrl;
  3.1252 +          } else if (kmzMetaData[imgUrlIE]) {
  3.1253 +             imgArray[i].src = kmzMetaData[imgUrlIE].dataUrl;
  3.1254 +          }
  3.1255 +        }
  3.1256 +      });
  3.1257 +      iW.open(this.map, this.bounds ? null : this);
  3.1258 +    });
  3.1259 +
  3.1260 +  }
  3.1261 +
  3.1262 +  return {
  3.1263 +    // Expose some properties and methods
  3.1264 +
  3.1265 +    options:     parserOptions,
  3.1266 +    docs:        docs,
  3.1267 +    docsByUrl:   docsByUrl,
  3.1268 +    kmzMetaData: kmzMetaData,
  3.1269 +
  3.1270 +    parse:          parse,
  3.1271 +    parseKmlString: parseKmlString,
  3.1272 +    hideDocument:   hideDocument,
  3.1273 +    showDocument:   showDocument,
  3.1274 +    processStyles:  processStyles,
  3.1275 +    createMarker:   createMarker,
  3.1276 +    createOverlay:  createOverlay,
  3.1277 +    createPolyline: createPolyline,
  3.1278 +    createPolygon:  createPolygon
  3.1279 +  };
  3.1280 +};
  3.1281 +// End of KML Parser
  3.1282 +
  3.1283 +// Helper objects and functions
  3.1284 +geoXML3.getOpacity = function (kmlColor) {
  3.1285 +  // Extract opacity encoded in a KML color value. Returns a number between 0 and 1.
  3.1286 +  if (!!kmlColor &&
  3.1287 +      (kmlColor !== '') &&
  3.1288 +      (kmlColor.length == 8)) {
  3.1289 +    var transparency = parseInt(kmlColor.substr(0, 2), 16);
  3.1290 +    return transparency / 255;
  3.1291 +  } else {
  3.1292 +    return 1;
  3.1293 +  }
  3.1294 +};
  3.1295 +
  3.1296 +// Log a message to the debugging console, if one exists
  3.1297 +geoXML3.log = function(msg) {
  3.1298 +  if (!!window.console) {
  3.1299 +    console.log(msg);
  3.1300 +  } else { alert("log:"+msg); }
  3.1301 +};
  3.1302 +
  3.1303 +/**
  3.1304 + * Creates a new parserOptions object.
  3.1305 + * @class GeoXML3 parser options.
  3.1306 + * @param {Object} overrides Any options you want to declare outside of the defaults should be included here.
  3.1307 + * @property {google.maps.Map} map The API map on which geo objects should be rendered.
  3.1308 + * @property {google.maps.MarkerOptions} markerOptions If the parser is adding Markers to the map itself, any options specified here will be applied to them.
  3.1309 + * @property {google.maps.InfoWindowOptions} infoWindowOptions If the parser is adding Markers to the map itself, any options specified here will be applied to their attached InfoWindows.
  3.1310 + * @property {ProjectedOverlay.options} overlayOptions If the parser is adding ProjectedOverlays to the map itself, any options specified here will be applied to them.
  3.1311 + */
  3.1312 +geoXML3.parserOptions = function (overrides) {
  3.1313 +  this.map                 = null,
  3.1314 +  /** If true, the parser will automatically move the map to a best-fit of the geodata after parsing of a KML document completes.
  3.1315 +   * @type Boolean
  3.1316 +   * @default true
  3.1317 +   */
  3.1318 +  this.zoom                = true,
  3.1319 +  /**#@+ @type Boolean
  3.1320 +   *     @default false */
  3.1321 +  /** 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). */
  3.1322 +  this.singleInfoWindow    = false,
  3.1323 +  /** If true, suppresses the rendering of info windows. */
  3.1324 +  this.suppressInfoWindows = false,
  3.1325 +  /**
  3.1326 +   * Control whether to process styles now or later.
  3.1327 +   *
  3.1328 +   * <p>By default, the parser only processes KML &lt;Style&gt; elements into their GMaps equivalents
  3.1329 +   * if it will be creating its own Markers (the createMarker option is null). Setting this option
  3.1330 +   * to true will force such processing to happen anyway, useful if you're going to be calling parser.createMarker
  3.1331 +   * yourself later. OTOH, leaving this option false removes runtime dependency on the GMaps API, enabling
  3.1332 +   * the use of geoXML3 as a standalone KML parser.</p>
  3.1333 +   */
  3.1334 +  this.processStyles       = false,
  3.1335 +  /**#@-*/
  3.1336 +
  3.1337 +  this.markerOptions       = {},
  3.1338 +  this.infoWindowOptions   = {},
  3.1339 +  this.overlayOptions      = {},
  3.1340 +
  3.1341 +  /**#@+ @event */
  3.1342 +  /** This function will be called when parsing of a KML document is complete.
  3.1343 +   * @param {geoXML3.parser#docs} doc Parsed KML data. */
  3.1344 +  this.afterParse          = null,
  3.1345 +  /** This function will be called when parsing of a KML document is complete.
  3.1346 +   * @param {geoXML3.parser#docs} doc Parsed KML data. */
  3.1347 +  this.failedParse         = null,
  3.1348 +  /**
  3.1349 +   * 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.
  3.1350 +   * @param {geoXML3.parser.render#placemark} placemark Placemark object.
  3.1351 +   * @param {geoXML3.parser#docs} doc Parsed KML data.
  3.1352 +   */
  3.1353 +  this.createMarker        = null,
  3.1354 +  /**
  3.1355 +   * 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.
  3.1356 +   * @param {geoXML3.parser.render#groundOverlay} groundOverlay GroundOverlay object.
  3.1357 +   * @param {geoXML3.parser#docs} doc Parsed KML data.
  3.1358 +   */
  3.1359 +  this.createOverlay       = null
  3.1360 +  /**#@-*/
  3.1361 +
  3.1362 +  if (overrides) {
  3.1363 +    for (var prop in overrides) {
  3.1364 +      if (overrides.hasOwnProperty(prop)) this[prop] = overrides[prop];
  3.1365 +    }
  3.1366 +  }
  3.1367 +  return this;
  3.1368 +};
  3.1369 +
  3.1370 +/**
  3.1371 + * Combine two options objects: a set of default values and a set of override values.
  3.1372 + *
  3.1373 + * @deprecated This has been replaced with {@link geoXML3.parserOptions#combineOptions}.
  3.1374 + * @param {geoXML3.parserOptions|Object} overrides Override values.
  3.1375 + * @param {geoXML3.parserOptions|Object} defaults Default values.
  3.1376 + * @return {geoXML3.parserOptions} Combined result.
  3.1377 + */
  3.1378 +geoXML3.combineOptions = function (overrides, defaults) {
  3.1379 +  var result = {};
  3.1380 +  if (!!overrides) {
  3.1381 +    for (var prop in overrides) {
  3.1382 +      if (overrides.hasOwnProperty(prop))                              result[prop] = overrides[prop];
  3.1383 +    }
  3.1384 +  }
  3.1385 +  if (!!defaults) {
  3.1386 +    for (prop in defaults) {
  3.1387 +      if (defaults.hasOwnProperty(prop) && result[prop] === undefined) result[prop] = defaults[prop];
  3.1388 +    }
  3.1389 +  }
  3.1390 +  return result;
  3.1391 +};
  3.1392 +
  3.1393 +/**
  3.1394 + * Combine two options objects: a set of default values and a set of override values.
  3.1395 + *
  3.1396 + * @function
  3.1397 + * @param {geoXML3.parserOptions|Object} overrides Override values.
  3.1398 + * @param {geoXML3.parserOptions|Object} defaults Default values.
  3.1399 + * @return {geoXML3.parserOptions} Combined result.
  3.1400 + */
  3.1401 +geoXML3.parserOptions.prototype.combineOptions = geoXML3.combineOptions;
  3.1402 +
  3.1403 +// Retrieve an XML document from url and pass it to callback as a DOM document
  3.1404 +geoXML3.fetchers = [];
  3.1405 +
  3.1406 +/**
  3.1407 + * Parses a XML string.
  3.1408 + *
  3.1409 + * <p>Parses the given XML string and returns the parsed document in a
  3.1410 + * DOM data structure. This function will return an empty DOM node if
  3.1411 + * XML parsing is not supported in this browser.</p>
  3.1412 + *
  3.1413 + * @param {String} str XML string.
  3.1414 + * @return {Element|Document} DOM.
  3.1415 + */
  3.1416 +geoXML3.xmlParse = function (str) {
  3.1417 +  if (typeof ActiveXObject != 'undefined' && typeof GetObject != 'undefined') {
  3.1418 +    var doc = new ActiveXObject('Microsoft.XMLDOM');
  3.1419 +    doc.loadXML(str);
  3.1420 +    return doc;
  3.1421 +  }
  3.1422 +
  3.1423 +  if (typeof DOMParser != 'undefined') {
  3.1424 +    return (new DOMParser()).parseFromString(str, 'text/xml');
  3.1425 +  }
  3.1426 +
  3.1427 +  return createElement('div', null);
  3.1428 +}
  3.1429 +
  3.1430 +/**
  3.1431 + * Fetches a XML document.
  3.1432 + *
  3.1433 + * <p>Fetches/parses the given XML URL and passes the parsed document (in a
  3.1434 + * DOM data structure) to the given callback.  Documents are downloaded
  3.1435 + * and parsed asynchronously.</p>
  3.1436 + *
  3.1437 + * @param {String} url URL of XML document.  Must be uncompressed XML only.
  3.1438 + * @param {Function(Document)} callback Function to call when the document is processed.
  3.1439 + */
  3.1440 +geoXML3.fetchXML = function (url, callback) {
  3.1441 +  function timeoutHandler() { callback(); };
  3.1442 +
  3.1443 +  var xhrFetcher = new Object();
  3.1444 +  if      (!!geoXML3.fetchers.length) xhrFetcher = geoXML3.fetchers.pop();
  3.1445 +  else if (!!window.XMLHttpRequest)   xhrFetcher.fetcher = new window.XMLHttpRequest();  // Most browsers
  3.1446 +  else if (!!window.ActiveXObject) {                                                     // Some IE
  3.1447 +    // the many versions of IE's XML fetchers
  3.1448 +    var AXOs = [
  3.1449 +      'MSXML2.XMLHTTP.6.0',
  3.1450 +      'MSXML2.XMLHTTP.5.0',
  3.1451 +      'MSXML2.XMLHTTP.4.0',
  3.1452 +      'MSXML2.XMLHTTP.3.0',
  3.1453 +      'MSXML2.XMLHTTP',
  3.1454 +      'Microsoft.XMLHTTP',
  3.1455 +      'MSXML.XMLHTTP'
  3.1456 +    ];
  3.1457 +    for (var i = 0; i < AXOs.length; i++) {
  3.1458 +      try      { xhrFetcher.fetcher = new ActiveXObject(AXOs[i]); break; }
  3.1459 +      catch(e) { continue; }
  3.1460 +    }
  3.1461 +    if (!xhrFetcher.fetcher) {
  3.1462 +      geoXML3.log('Unable to create XHR object');
  3.1463 +      callback(null);
  3.1464 +      return null;
  3.1465 +    }
  3.1466 +  }
  3.1467 +
  3.1468 +  if (!!xhrFetcher.fetcher.overrideMimeType) xhrFetcher.fetcher.overrideMimeType('text/xml');
  3.1469 +  xhrFetcher.fetcher.open('GET', url, true);
  3.1470 +  xhrFetcher.fetcher.onreadystatechange = function () {
  3.1471 +    if (xhrFetcher.fetcher.readyState === 4) {
  3.1472 +      // Retrieval complete
  3.1473 +      if (!!xhrFetcher.xhrtimeout) clearTimeout(xhrFetcher.xhrtimeout);
  3.1474 +      if (xhrFetcher.fetcher.status >= 400) {
  3.1475 +        geoXML3.log('HTTP error ' + xhrFetcher.fetcher.status + ' retrieving ' + url);
  3.1476 +        callback();
  3.1477 +      }
  3.1478 +      // Returned successfully
  3.1479 +      else {
  3.1480 +        if (xhrFetcher.fetcher.responseXML) {
  3.1481 +        // Sometimes IE will get the data, but won't bother loading it as an XML doc
  3.1482 +        var xmlDoc = xhrFetcher.fetcher.responseXML;
  3.1483 +        if (xmlDoc && !xmlDoc.documentElement && !xmlDoc.ownerElement) xmlDoc.loadXML(xhrFetcher.fetcher.responseText);
  3.1484 +          callback(xmlDoc);          
  3.1485 +        } else // handle valid xml sent with wrong MIME type 
  3.1486 +          callback(geoXML3.xmlParse(xhrFetcher.fetcher.responseText));
  3.1487 +      }
  3.1488 +
  3.1489 +      // We're done with this fetcher object
  3.1490 +      geoXML3.fetchers.push(xhrFetcher);
  3.1491 +    }
  3.1492 +  };
  3.1493 +
  3.1494 +  xhrFetcher.xhrtimeout = setTimeout(timeoutHandler, 60000);
  3.1495 +  xhrFetcher.fetcher.send(null);
  3.1496 +  return null;
  3.1497 +};
  3.1498 +
  3.1499 +var IEversion = function() {
  3.1500 +  // http://msdn.microsoft.com/workshop/author/dhtml/overview/browserdetection.asp
  3.1501 +  // Returns the version of Internet Explorer or a -1
  3.1502 +  // (indicating the use of another browser).
  3.1503 +  var rv = -1; // Return value assumes failure
  3.1504 +  if (navigator.appName == 'Microsoft Internet Explorer') {
  3.1505 +    var ua = navigator.userAgent;
  3.1506 +    var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
  3.1507 +    if (re.exec(ua) != null) {
  3.1508 +      rv = parseFloat( RegExp.$1 );
  3.1509 +    }
  3.1510 +  }
  3.1511 +  return rv;
  3.1512 +};
  3.1513 +
  3.1514 +/**
  3.1515 + * Fetches a KMZ document.
  3.1516 + *
  3.1517 + * <p>Fetches/parses the given ZIP URL, parses each image file, and passes
  3.1518 + * the parsed KML document to the given callback.  Documents are downloaded
  3.1519 + * and parsed asynchronously, though the KML file is always passed after the
  3.1520 + * images have been processed, in case the callback requires the image data.</p>
  3.1521 + *
  3.1522 + * @requires ZipFile.complete.js
  3.1523 + * @param {String} url URL of KMZ document.  Must be a valid KMZ/ZIP archive.
  3.1524 + * @param {Function(Document)} callback Function to call when the document is processed.
  3.1525 + * @param {geoXML3.parser} parser A geoXML3.parser object.  This is used to populate the KMZ image data.
  3.1526 + * @author Brendan Byrd
  3.1527 + * @see http://code.google.com/apis/kml/documentation/kmzarchives.html
  3.1528 + */
  3.1529 +geoXML3.fetchZIP = function (url, callback, parser) {
  3.1530 +  // Just need a single 'new' declaration with a really long function...
  3.1531 +  var zipFile = new ZipFile(url, function (zip) {
  3.1532 +    // Retrieval complete
  3.1533 +
  3.1534 +    // Check for ERRORs in zip.status
  3.1535 +    for (var i = 0; i < zip.status.length; i++) {
  3.1536 +      var msg = zip.status[i];
  3.1537 +      if      (msg.indexOf("ERROR") == 0) {
  3.1538 +        geoXML3.log('HTTP/ZIP error retrieving ' + url + ': ' + msg);
  3.1539 +        callback();
  3.1540 +        return;
  3.1541 +      }
  3.1542 +      else if (msg.indexOf("WARNING") == 0) {  // non-fatal, but still might be useful
  3.1543 +        geoXML3.log('HTTP/ZIP warning retrieving ' + url + ': ' + msg);
  3.1544 +      }
  3.1545 +    }
  3.1546 +
  3.1547 +    // Make sure KMZ structure is according to spec (with a single KML file in the root dir)
  3.1548 +    var KMLCount = 0;
  3.1549 +    var KML;
  3.1550 +    for (var i = 0; i < zip.entries.length; i++) {
  3.1551 +      var name = zip.entries[i].name;
  3.1552 +      if (!/\.kml$/.test(name)) continue;
  3.1553 +
  3.1554 +      KMLCount++;
  3.1555 +      if (KMLCount == 1) KML = i;
  3.1556 +      else {
  3.1557 +        geoXML3.log('KMZ warning retrieving ' + url + ': found extra KML "' + name + '" in KMZ; discarding...');
  3.1558 +      }
  3.1559 +    }
  3.1560 +
  3.1561 +    // Returned successfully, but still needs extracting
  3.1562 +    var baseUrl = cleanURL(defileURL(url), url) + '/';
  3.1563 +    var kmlProcessing = {  // this is an object just so it gets passed properly
  3.1564 +      timer: null,
  3.1565 +      extractLeft: 0,
  3.1566 +      timerCalls: 0
  3.1567 +    };
  3.1568 +    var extractCb = function(entry, entryContent) {
  3.1569 +      var mdUrl = cleanURL(baseUrl, entry.name);
  3.1570 +      var ext = entry.name.substring(entry.name.lastIndexOf(".") + 1).toLowerCase();
  3.1571 +      kmlProcessing.extractLeft--;
  3.1572 +
  3.1573 +      if ((typeof entryContent.description == "string") && (entryContent.name == "Error")) {
  3.1574 +        geoXML3.log('KMZ error extracting ' + mdUrl + ': ' + entryContent.description);
  3.1575 +        callback();
  3.1576 +        return;
  3.1577 +      }
  3.1578 +
  3.1579 +      // MIME types that can be used in KML
  3.1580 +      var mime;
  3.1581 +      if (ext === 'jpg') ext = 'jpeg';
  3.1582 +      if (/^(gif|jpeg|png)$/.test(ext)) mime = 'image/' + ext;
  3.1583 +      else if (ext === 'mp3')           mime = 'audio/mpeg';
  3.1584 +      else if (ext === 'm4a')           mime = 'audio/mp4';
  3.1585 +      else if (ext === 'm4a')           mime = 'audio/MP4-LATM';
  3.1586 +      else                              mime = 'application/octet-stream';
  3.1587 +
  3.1588 +      parser.kmzMetaData[mdUrl] = {};
  3.1589 +      parser.kmzMetaData[mdUrl].entry = entry;
  3.1590 +      // data:image/gif;base64,R0lGODlhEAAOALMA...
  3.1591 +      parser.kmzMetaData[mdUrl].dataUrl = 'data:' + mime + ';base64,' + base64Encode(entryContent);
  3.1592 +      // IE cannot handle GET requests beyond 2071 characters, even if it's an inline image
  3.1593 +	if (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent))
  3.1594 +        { 
  3.1595 +            if (((IEversion() < 8.0) &&
  3.1596 +                 (parser.kmzMetaData[mdUrl].dataUrl.length > 2071)) ||
  3.1597 +                ((IEversion < 9.0) && 
  3.1598 +                 (parser.kmzMetaData[mdUrl].dataUrl.length > 32767))) {
  3.1599 +             parser.kmzMetaData[mdUrl].dataUrl =
  3.1600 +             // this is a simple IE icon; to hint at the problem...
  3.1601 +             'data:image/gif;base64,R0lGODlhDwAQAOMPADBPvSpQ1Dpoyz1p6FhwvU2A6ECP63CM04CWxYCk+V6x+UK++Jao3rvC3fj7+v///yH5BAEKAA8ALAAAAAAPABAAAASC8Mk5mwCAUMlWwcLRHEelLA' +
  3.1602 +             'oGDMgzSsiyGCAhCETDPMh5XQCBwYBrNBIKWmg0MCQHj8MJU5IoroYCY6AAAgrDIbbQDGIK6DR5UPhlNo0JAlSUNAiDgH7eNAxEDWAKCQM2AAFheVxYAA0AIkFOJ1gBcQQaUQKKA5w7LpcEBwkJaKMUEQA7';
  3.1603 +            } 
  3.1604 +       }
  3.1605 +       parser.kmzMetaData[internalSrc(entry.name)]=parser.kmzMetaData[mdUrl];	
  3.1606 +
  3.1607 +    };
  3.1608 +    var kmlExtractCb = function(entry, entryContent) {
  3.1609 +      if ((typeof entryContent.description == "string") && (entryContent.name == "Error")) {
  3.1610 +        geoXML3.log('KMZ error extracting ' + mdUrl + ': ' + entryContent.description);
  3.1611 +        callback();
  3.1612 +        return;
  3.1613 +      }
  3.1614 +
  3.1615 +      // check to see if the KML is the last file extracted
  3.1616 +      clearTimeout(kmlProcessing.timer);
  3.1617 +      if (kmlProcessing.extractLeft <= 1) {
  3.1618 +        kmlProcessing.extractLeft--;
  3.1619 +        callback(geoXML3.xmlParse(entryContent));
  3.1620 +        return;
  3.1621 +      }
  3.1622 +      else {
  3.1623 +        // KML file isn't last yet; it may need to use those files, so wait a bit (100ms)
  3.1624 +        kmlProcessing.timerCalls++;
  3.1625 +        if (kmlProcessing.timerCalls < 100) {
  3.1626 +          kmlProcessing.timer = setTimeout(function() { kmlExtractCb(entry, entryContent); }, 100);
  3.1627 +        }
  3.1628 +        else {
  3.1629 +          geoXML3.log('KMZ warning extracting ' + url + ': entire ZIP has not been extracted after 10 seconds; running through KML, anyway...');
  3.1630 +          kmlProcessing.extractLeft--;
  3.1631 +          callback(geoXML3.xmlParse(entryContent));
  3.1632 +        }
  3.1633 +      }
  3.1634 +      return;
  3.1635 +    };
  3.1636 +    for (var i = 0; i < zip.entries.length; i++) {
  3.1637 +      var entry = zip.entries[i];
  3.1638 +      var ext = entry.name.substring(entry.name.lastIndexOf(".") + 1).toLowerCase();
  3.1639 +      if (!/^(gif|jpe?g|png|kml)$/.test(ext)) continue;  // not going to bother to extract files we don't support
  3.1640 +      if (ext === "kml" && i != KML)          continue;  // extra KMLs get discarded
  3.1641 +      if (!parser && ext != "kml")            continue;  // cannot store images without a parser object
  3.1642 +
  3.1643 +      // extract asynchronously
  3.1644 +      kmlProcessing.extractLeft++;
  3.1645 +      if (ext === "kml") entry.extract(kmlExtractCb);
  3.1646 +      else               entry.extract(extractCb);
  3.1647 +    }
  3.1648 +  });
  3.1649 +
  3.1650 +};
  3.1651 +
  3.1652 +/**
  3.1653 + * Extract the text value of a DOM node, with leading and trailing whitespace trimmed.
  3.1654 + *
  3.1655 + * @param {Element} node XML node/element.
  3.1656 + * @param {Any} delVal Default value if the node doesn't exist.
  3.1657 + * @return {String|Null}
  3.1658 + */
  3.1659 +geoXML3.nodeValue = function(node, defVal) {
  3.1660 +  var retStr="";
  3.1661 +  if (!node) {
  3.1662 +    return (typeof defVal === 'undefined' || defVal === null) ? null : defVal;
  3.1663 +  }
  3.1664 +   if(node.nodeType==3||node.nodeType==4||node.nodeType==2){
  3.1665 +      retStr+=node.nodeValue;
  3.1666 +   }else if(node.nodeType==1||node.nodeType==9||node.nodeType==11){
  3.1667 +      for(var i=0;i<node.childNodes.length;++i){
  3.1668 +         retStr+=arguments.callee(node.childNodes[i]);
  3.1669 +      }
  3.1670 +   }
  3.1671 +   return retStr;
  3.1672 +};
  3.1673 +
  3.1674 +/**
  3.1675 + * Loosely translate various values of a DOM node to a boolean.
  3.1676 + *
  3.1677 + * @param {Element} node XML node/element.
  3.1678 + * @param {Boolean} delVal Default value if the node doesn't exist.
  3.1679 + * @return {Boolean|Null}
  3.1680 + */
  3.1681 +geoXML3.getBooleanValue = function(node, defVal) {
  3.1682 +  var nodeContents = geoXML3.nodeValue(node);
  3.1683 +  if (nodeContents === null) return defVal || false;
  3.1684 +  nodeContents = parseInt(nodeContents);
  3.1685 +  if (isNaN(nodeContents)) return true;
  3.1686 +  if (nodeContents == 0) return false;
  3.1687 +  else return true;
  3.1688 +}
  3.1689 +
  3.1690 +/**
  3.1691 + * Browser-normalized version of getElementsByTagNameNS.
  3.1692 + *
  3.1693 + * <p>Required because IE8 doesn't define it.</p>
  3.1694 + *
  3.1695 + * @param {Element|Document} node DOM object.
  3.1696 + * @param {String} namespace Full namespace URL to search against.
  3.1697 + * @param {String} tagname XML local tag name.
  3.1698 + * @return {Array of Elements}
  3.1699 + * @author Brendan Byrd
  3.1700 + */
  3.1701 +geoXML3.getElementsByTagNameNS = function(node, namespace, tagname) {
  3.1702 +  if (node && typeof node.getElementsByTagNameNS != 'undefined') return node.getElementsByTagNameNS(namespace, tagname);
  3.1703 +  if (!node) return [];
  3.1704 +
  3.1705 +  var root = node.documentElement || node.ownerDocument && node.ownerDocument.documentElement;
  3.1706 +  if (!root || !root.attributes) return [];
  3.1707 +
  3.1708 +  // search for namespace prefix
  3.1709 +  for (var i = 0; i < root.attributes.length; i++) {
  3.1710 +    var attr = root.attributes[i];
  3.1711 +    if      (attr.prefix   === 'xmlns' && attr.nodeValue === namespace) return node.getElementsByTagName(attr.baseName + ':' + tagname);
  3.1712 +    else if (attr.nodeName === 'xmlns' && attr.nodeValue === namespace) {
  3.1713 +      // default namespace
  3.1714 +      if (typeof node.selectNodes != 'undefined') {
  3.1715 +        // Newer IEs have the SelectionNamespace property that can be used with selectNodes
  3.1716 +        if (!root.ownerDocument.getProperty('SelectionNamespaces'))
  3.1717 +          root.ownerDocument.setProperty('SelectionNamespaces', "xmlns:defaultNS='" + namespace + "'");
  3.1718 +        return node.selectNodes('.//defaultNS:' + tagname);
  3.1719 +      }
  3.1720 +      else {
  3.1721 +        // Otherwise, you can still try to tack on the 'xmlns' attribute to root
  3.1722 +        root.setAttribute('xmlns:defaultNS', namespace);
  3.1723 +        return node.getElementsByTagName('defaultNS:' + tagname);
  3.1724 +      }
  3.1725 +    }
  3.1726 +  }
  3.1727 +  return geoXML3.getElementsByTagName(node, tagname);  // try the unqualified version
  3.1728 +};
  3.1729 +
  3.1730 +/**
  3.1731 + * Browser-normalized version of getElementsByTagName.
  3.1732 + *
  3.1733 + * <p>Required because MSXML 6.0 will treat this function as a NS-qualified function,
  3.1734 + * despite the missing NS parameter.</p>
  3.1735 + *
  3.1736 + * @param {Element|Document} node DOM object.
  3.1737 + * @param {String} tagname XML local tag name.
  3.1738 + * @return {Array of Elements}
  3.1739 + * @author Brendan Byrd
  3.1740 + */
  3.1741 +geoXML3.getElementsByTagName = function(node, tagname) {
  3.1742 +  if (node && typeof node.getElementsByTagNameNS != 'undefined') return node.getElementsByTagName(tagname);  // if it has both functions, it should be accurate
  3.1743 +//  if (node && typeof node.selectNodes != 'undefined')            return node.selectNodes(".//*[local-name()='" + tagname + "']");
  3.1744 +  return node.getElementsByTagName(tagname);  // hope for the best...
  3.1745 +}
  3.1746 +
  3.1747 +/**
  3.1748 + * Turn a directory + relative URL into an absolute one.
  3.1749 + *
  3.1750 + * @private
  3.1751 + * @param {String} d Base directory.
  3.1752 + * @param {String} s Relative URL.
  3.1753 + * @return {String} Absolute URL.
  3.1754 + * @author Brendan Byrd
  3.1755 + */
  3.1756 +var toAbsURL = function (d, s) {
  3.1757 +  var p, f, i;
  3.1758 +  var h = location.protocol + "://" + location.host;
  3.1759 +
  3.1760 +  if (!s.length)           return '';
  3.1761 +  if (/^\w+:/.test(s))     return s;
  3.1762 +  if (s.indexOf('/') == 0) return h + s;
  3.1763 +
  3.1764 +  p = d.replace(/\/[^\/]*$/, '');
  3.1765 +  f = s.match(/\.\.\//g);
  3.1766 +  if (f) {
  3.1767 +    s = s.substring(f.length * 3);
  3.1768 +    for (i = f.length; i--;) { p = p.substring(0, p.lastIndexOf('/')); }
  3.1769 +  }
  3.1770 +
  3.1771 +  return h + p + '/' + s;
  3.1772 +}
  3.1773 +
  3.1774 +var internalSrc = function(src) {
  3.1775 +  //this gets the full url
  3.1776 +  var url = document.location.href;
  3.1777 +  //this removes everything after the last slash in the path
  3.1778 +  url = url.substring(0,url.lastIndexOf("/") + 1);
  3.1779 +  var internalPath= url+src;
  3.1780 +  return internalPath;
  3.1781 +}
  3.1782 +
  3.1783 +/**
  3.1784 + * Remove current host from URL
  3.1785 + *
  3.1786 + * @private
  3.1787 + * @param {String} s Absolute or relative URL.
  3.1788 + * @return {String} Root-based relative URL.
  3.1789 + * @author Brendan Byrd
  3.1790 + */
  3.1791 +var dehostURL = function (s) {
  3.1792 +  var h = location.protocol + "://" + location.host;
  3.1793 +  h = h.replace(/([\.\\\+\*\?\[\^\]\$\(\)])/g, '\\$1');  // quotemeta
  3.1794 +  return s.replace(new RegExp('^' + h, 'i'), '');
  3.1795 +}
  3.1796 +
  3.1797 +/**
  3.1798 + * Removes all query strings, #IDs, '../' references, and
  3.1799 + * hosts from a URL.
  3.1800 + *
  3.1801 + * @private
  3.1802 + * @param {String} d Base directory.
  3.1803 + * @param {String} s Absolute or relative URL.
  3.1804 + * @return {String} Root-based relative URL.
  3.1805 + * @author Brendan Byrd
  3.1806 + */
  3.1807 +var cleanURL  = function (d, s) { return dehostURL(toAbsURL(d ? d.split('#')[0].split('?')[0] : defileURL(location.pathname), s ? s.split('#')[0].split('?')[0] : '')); }
  3.1808 +/**
  3.1809 + * Remove filename from URL
  3.1810 + *
  3.1811 + * @private
  3.1812 + * @param {String} s Relative URL.
  3.1813 + * @return {String} Base directory.
  3.1814 + * @author Brendan Byrd
  3.1815 + */
  3.1816 +var defileURL = function (s)    { return s ? s.substr(0, s.lastIndexOf('/') + 1) : '/'; }
  3.1817 +
  3.1818 +
  3.1819 +// Some extra Array subs for ease of use
  3.1820 +// http://stackoverflow.com/questions/143847/best-way-to-find-an-item-in-a-javascript-array
  3.1821 +Array.prototype.hasObject = (
  3.1822 +  !Array.indexOf ? function (obj) {
  3.1823 +    var l = this.length + 1;
  3.1824 +    while (l--) {
  3.1825 +      if (this[l - 1] === obj) return true;
  3.1826 +    }
  3.1827 +    return false;
  3.1828 +  } : function (obj) {
  3.1829 +    return (this.indexOf(obj) !== -1);
  3.1830 +  }
  3.1831 +);
  3.1832 +Array.prototype.hasItemInObj = function (name, item) {
  3.1833 +  var l = this.length + 1;
  3.1834 +  while (l--) {
  3.1835 +    if (this[l - 1][name] === item) return true;
  3.1836 +  }
  3.1837 +  return false;
  3.1838 +};
  3.1839 +if (!Array.prototype.indexOf) {
  3.1840 +  Array.prototype.indexOf = function (obj, fromIndex) {
  3.1841 +    if (fromIndex == null) {
  3.1842 +      fromIndex = 0;
  3.1843 +    } else if (fromIndex < 0) {
  3.1844 +      fromIndex = Math.max(0, this.length + fromIndex);
  3.1845 +    }
  3.1846 +    for (var i = fromIndex, j = this.length; i < j; i++) {
  3.1847 +      if (this[i] === obj) return i;
  3.1848 +    }
  3.1849 +    return -1;
  3.1850 +  };
  3.1851 +}
  3.1852 +Array.prototype.indexOfObjWithItem = function (name, item, fromIndex) {
  3.1853 +  if (fromIndex == null) {
  3.1854 +    fromIndex = 0;
  3.1855 +  } else if (fromIndex < 0) {
  3.1856 +    fromIndex = Math.max(0, this.length + fromIndex);
  3.1857 +  }
  3.1858 +  for (var i = fromIndex, j = this.length; i < j; i++) {
  3.1859 +    if (this[i][name] === item) return i;
  3.1860 +  }
  3.1861 +  return -1;
  3.1862 +};
  3.1863 +
  3.1864 +/**
  3.1865 + * Borrowed from jquery.base64.js, with some "Array as input" corrections
  3.1866 + *
  3.1867 + * @private
  3.1868 + * @param {Array of charCodes} input An array of byte ASCII codes (0-255).
  3.1869 + * @return {String} A base64-encoded string.
  3.1870 + * @author Brendan Byrd
  3.1871 + */
  3.1872 +var base64Encode = function(input) {
  3.1873 +  var keyString = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
  3.1874 +  var output = "";
  3.1875 +  var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
  3.1876 +  var i = 0;
  3.1877 +  while (i < input.length) {
  3.1878 +    chr1 = input[i++];
  3.1879 +    chr2 = input[i++];
  3.1880 +    chr3 = input[i++];
  3.1881 +    enc1 = chr1 >> 2;
  3.1882 +    enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
  3.1883 +    enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
  3.1884 +    enc4 = chr3 & 63;
  3.1885 +
  3.1886 +    if      (chr2 == undefined) enc3 = enc4 = 64;
  3.1887 +    else if (chr3 == undefined) enc4 = 64;
  3.1888 +
  3.1889 +    output = output + keyString.charAt(enc1) + keyString.charAt(enc2) + keyString.charAt(enc3) + keyString.charAt(enc4);
  3.1890 +  }
  3.1891 +  return output;
  3.1892 +};
     4.1 --- a/endpoint/WebContent/query.jsp	Mon Sep 10 15:38:09 2012 +0300
     4.2 +++ b/endpoint/WebContent/query.jsp	Fri Sep 14 13:03:24 2012 +0300
     4.3 @@ -5,9 +5,7 @@
     4.4  <head>
     4.5  	<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
     4.6  	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
     4.7 -	<link href="http://code.google.com/apis/maps/documentation/javascript/examples/default.css" rel="stylesheet" type="text/css" />
     4.8 -	<link rel="stylesheet" href="style.css" type="text/css" /> 
     4.9 -	<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>
    4.10 +	<link rel="stylesheet" href="style.css" type="text/css" />
    4.11  	<script type="text/javascript">
    4.12  		function toggleMe(a) {
    4.13  			var e = document.getElementById(a);
    4.14 @@ -22,21 +20,41 @@
    4.15  			return true;
    4.16  		}
    4.17  	</script>
    4.18 +<% if (request.getAttribute("pathToKML") != null) {
    4.19 +	if ("map_local".equals(request.getAttribute("handle"))) { %>
    4.20 +	<script type="text/javascript" src="js/geoxml3-kmz.js"></script>
    4.21 +	<script type="text/javascript" src="js/ProjectedOverlay.js"></script>	
    4.22 +	<%} %>
    4.23 +	<link href="http://code.google.com/apis/maps/documentation/javascript/examples/default.css" rel="stylesheet" type="text/css" />
    4.24 +	<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>
    4.25  	<script type="text/javascript">
    4.26  		function initialize() {
    4.27 +			// center at Brahames
    4.28  			var brahames = new google.maps.LatLng(37.92253, 23.72275);
    4.29  			var myOptions = {
    4.30  				zoom: 11,
    4.31  				center: brahames,
    4.32  				mapTypeId: google.maps.MapTypeId.ROADMAP
    4.33  			};
    4.34 -		
    4.35 +			
    4.36 +			// get KML filename
    4.37 +			var kml = '<%=request.getAttribute("pathToKML")%>';
    4.38 +			// <%=request.getAttribute("handle")%>
    4.39 +			// create map
    4.40  			var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
    4.41  		
    4.42 -			var ctaLayer = new google.maps.KmlLayer('<%=request.getAttribute("pathToKML") != null ? request.getAttribute("pathToKML"):""%>');
    4.43 +			// display using geoxml3
    4.44 +		<%if ("map_local".equals(request.getAttribute("handle"))) { %>
    4.45 +			var myParser = new geoXML3.parser({map: map});
    4.46 +			myParser.parse(kml);
    4.47 +			
    4.48 +		<%} else {%>
    4.49 +			var ctaLayer = new google.maps.KmlLayer(kml);
    4.50  			ctaLayer.setMap(map);
    4.51 +		<%}%>
    4.52  		}
    4.53  	</script> 
    4.54 +<%}%>
    4.55  	<title>TELEIOS: Strabon Endpoint</title>
    4.56  </head>
    4.57  <body topmargin="0" leftmargin="0" link="#FFFFFF" vlink="#FFFFFF" alink="#FFFFFF" onload="initialize()">
    4.58 @@ -101,6 +119,7 @@
    4.59  		<OPTION value="plain">Plain result</OPTION>
    4.60  		<OPTION value="download">Download</OPTION>
    4.61  		<OPTION value="map">On a map</OPTION>
    4.62 +		<OPTION value="map_local">On a map (localhost)</OPTION>
    4.63  	</SELECT></center>
    4.64  	</td>
    4.65  	<td colspan=2>&nbsp;</td>
     5.1 --- a/endpoint/pom.xml	Mon Sep 10 15:38:09 2012 +0300
     5.2 +++ b/endpoint/pom.xml	Fri Sep 14 13:03:24 2012 +0300
     5.3 @@ -195,6 +195,7 @@
     5.4  							<directory>${basedir}/WebContent</directory>
     5.5  							<includes>
     5.6  								<include>images/**</include>
     5.7 +								<include>js/**</include>
     5.8  							</includes>
     5.9  						</resource>
    5.10  					</webResources>
     6.1 --- a/endpoint/src/main/java/eu/earthobservatory/org/StrabonEndpoint/QueryBean.java	Mon Sep 10 15:38:09 2012 +0300
     6.2 +++ b/endpoint/src/main/java/eu/earthobservatory/org/StrabonEndpoint/QueryBean.java	Fri Sep 14 13:03:24 2012 +0300
     6.3 @@ -208,7 +208,7 @@
     6.4  				    
     6.5  				    out.flush();
     6.6  				    
     6.7 -				} else if ("map".equals(handle) && 
     6.8 +				} else if (("map".equals(handle) || "map_local".equals(handle)) && 
     6.9  						(queryResultFormat == stSPARQLQueryResultFormat.KML || 
    6.10  						 queryResultFormat == stSPARQLQueryResultFormat.KMZ) ) {
    6.11  					// show map (only valid for KML/KMZ)
    6.12 @@ -216,6 +216,9 @@
    6.13  					// get dispatcher
    6.14  					dispatcher = request.getRequestDispatcher("query.jsp");
    6.15  					
    6.16 +					// re-assign handle
    6.17 +					request.setAttribute("handle", handle);
    6.18 +					
    6.19  					SecureRandom random = new SecureRandom();
    6.20  					String temp = new BigInteger(130, random).toString(32);
    6.21