function getOverlayArray(xml, icon, infoWindowOpts) {
    xml = getOverlayArray.arguments[0];
    if (getOverlayArray.arguments.length > 1) {
        icon = getOverlayArray.arguments[1];
    } else {
        icon = null;
    }
    if (getOverlayArray.arguments.length > 2) {
        infoWindowOpts = getOverlayArray.arguments[2];
    } else {
        infoWindowOpts = null;
    }

    // <Placemark> => GMarker/GPolygon
    // @see http://code.google.com/intl/de-DE/apis/kml/documentation/kmlreference.html#placemark
    // @see http://code.google.com/intl/de/apis/maps/documentation/reference.html#GMarker
    // @see http://code.google.com/intl/de/apis/maps/documentation/reference.html#GPolygon
    var result = Array();

    // Style[]
    var styleArray = getStyleArray(xml);

    var kmlPlacemarkArray = xml.documentElement.getElementsByTagName("Placemark");
    for (var i = 0; i < kmlPlacemarkArray.length; i++) {
        var placemark = { 'type':'gmarker' };

        // <name />
        var gOptions = {};
        var name = kmlPlacemarkArray[i].getElementsByTagName("name");
        if (name.length > 0) {
            gOptions.title = name[0].firstChild.data;
        }
        if (icon != null) {
            gOptions.icon = icon;
        }

        var pmMultiGeometry = kmlPlacemarkArray[i].getElementsByTagName("MultiGeometry")[0];

        // < MultiGeometry/Point/coordinates />
        var pmPoint = pmMultiGeometry.getElementsByTagName("Point")[0];
        if (pmPoint != null) {
            var pmPointCoordinates = pmMultiGeometry.getElementsByTagName("coordinates")[0].firstChild.data;
            var coordinateArray = pmPointCoordinates.split(",");
            var gLng = Number(coordinateArray[0]);
            var gLat = Number(coordinateArray[1]);

            placemark.overlay = new google.maps.Marker(new google.maps.LatLng(gLat, gLng, true), gOptions);

            // <description />
            var pmInfoWindow = kmlPlacemarkArray[i].getElementsByTagName("description")[0];
            placemark.overlay.bindInfoWindowHtml(getCDataContent(pmInfoWindow), infoWindowOpts);
            result.push(placemark);
        }

        // GPolygon
        var pmPolygonArray = pmMultiGeometry.getElementsByTagName("Polygon");
        if (pmPolygonArray != null) {
          for (var iP = 0; iP < pmPolygonArray.length; iP++) {
            var pmPolygon = pmPolygonArray[iP];
            var pmPolygonCoordinates = "";
            var childNodes = pmPolygon.getElementsByTagName("coordinates")[0].childNodes;
            for (var c = 0; c < childNodes.length; c++) {
                pmPolygonCoordinates += childNodes[c].data;
            }
            var latlngArray = Array();
            var pmPolygonCoordinateMatch = pmPolygonCoordinates.match(/[0-9,\.]*/g);
            for (var j = 0; j < pmPolygonCoordinateMatch.length; j++) {
                if (pmPolygonCoordinateMatch[j] != "") {
                    coordinateArray = pmPolygonCoordinateMatch[j].split(",");
                    gLng = Number(coordinateArray[0]);
                    gLat = Number(coordinateArray[1]);
                    latlngArray.push(new google.maps.LatLng(gLat, gLng, true));
                }
            }

            // <styleUrl />
            var pmPolyStyle = null;
            var pmStyleUrl = null;
            var styleUrlElement = kmlPlacemarkArray[i].getElementsByTagName("styleUrl");
            if (styleUrlElement.length > 0) {
                pmStyleUrl = styleUrlElement[0].firstChild.data;
            }
            if (pmStyleUrl != null) {
                pmStyleUrl = pmStyleUrl.trim();
                // TODO: no external Uri allowed
                if (pmStyleUrl.substring(0,1) == "#") {
                    pmPolyStyle = getObjectById(styleArray, pmStyleUrl.substring(1));
                }
            }
            var polygon = { 'type':'gpolygon' };
            if (pmPolyStyle != null) {
                polygon.overlay = new google.maps.Polygon(
                                        latlngArray,
                                        pmPolyStyle.line.color,
                                        pmPolyStyle.line.weight,
                                        pmPolyStyle.line.opacity,
                                        pmPolyStyle.poly.color,
                                        pmPolyStyle.poly.opacity);
            } else {
                polygon.overlay = new google.maps.Polygon(latlngArray);
            }
            result.push(polygon);
          }
        }
    }
    return result;
}

function getStyleArray(xml) {
    // <Style /> => GPolyStyleOptions
    // @see http://code.google.com/intl/de-DE/apis/kml/documentation/kmlreference.html#style
    // @see http://code.google.com/intl/de/apis/maps/documentation/reference.html#GPolyStyleOptions
    var result = new Array();
    var kmlStyleArray = xml.documentElement.getElementsByTagName("Style");
    for (var i = 0; i < kmlStyleArray.length; i++) {
        var kmlColor;
        var kmlWidth;
        var style = {'id':kmlStyleArray[i].getAttribute("id") };
        // LineStyle (color/colorMode/width)
        var kmlLineStyle = kmlStyleArray[i].getElementsByTagName("LineStyle")[0];
        if (kmlLineStyle != null) {
            style.line = {};
            kmlColor = kmlLineStyle.getElementsByTagName("color")[0].firstChild.data;
            if (kmlColor != null) {
                kmlColor = kmlColor.trim();
                style.line.color = "#" + kmlColor.substring(6,8) + kmlColor.substring(4,6) + kmlColor.substring(2,4);
                style.line.opacity = parseInt(kmlColor.substring(0,2), 16) / 255;
            }
            kmlWidth = kmlLineStyle.getElementsByTagName("width")[0].firstChild.data;
            if (kmlWidth != null) {
                style.line.weight = Number(kmlWidth);
            }
        }
        // PolyStyle (color/colorMode/fill/outline)
        var kmlPolyStyle = kmlStyleArray[i].getElementsByTagName("PolyStyle")[0];
        if (kmlPolyStyle != null) {
            style.poly = {};
            kmlColor = kmlPolyStyle.getElementsByTagName("color")[0].firstChild.data;
            if (kmlColor != null) {
                kmlColor = kmlColor.trim();
                style.poly.color = "#" + kmlColor.substring(6,8) + kmlColor.substring(4,6) + kmlColor.substring(2,4);
                style.poly.opacity = parseInt(kmlColor.substring(0,2), 16) / 255;
            }
        }
        // TODO: IconStyle
        // TODO: LabelStyle
        // TODO: BalloonStyle
        // TODO: ListStyle
        result.push(style);
    }
    return result;
}

function getObjectProperties(obj) { /* for DEBUGGING */
    var str = "";
    for (var prop in obj) {
        str += prop + " = " + obj[prop] + ",\n";
    }
    return str;
}

function getObjectById(array, id) {
    for (var i = 0; i < array.length; i++) {
        if (array[i].id == id) return array[i];
    }
    return null;
}

function getCDataContent(node) {
    for (var i = 0; i < node.childNodes.length; i++) {
        if (node.childNodes[i].nodeType == 4) return node.childNodes[i].data;
    }
    return null;
}

// Milinda Pathirage, http://mpathirage.com/solution-to-xml-namespace-problem-in-getelementsbytagname/
function getNamespaceElementsByTagName(tagName, ns, prefix, scope){
    var result = scope.getElementsByTagName(prefix + ":" + tagName);
    if (result.length == 0){
        result = scope.getElementsByTagName(tagName);
        if (result.length == 0){
            result = scope.getElementsByTagName("ns:" + tagName);
            if (result.length == 0 && document.getElementsByTagNameNS){
                result = scope.getElementsByTagNameNS(ns, tagName);
            }
        }
    }
    return result;
}

// Alexander Müller, http://www.evocomp.de/beispiele/javascript/trim.html
String.prototype.ltrim = function(clist) {
    if (clist) return this.replace(new RegExp('^[' + clist + ']+'), '');
    return this.replace(/^\s+/, '');
}
String.prototype.rtrim = function(clist) {
    if (clist) return this.replace(new RegExp('[' + clist + ']+$'), '');
    return this.replace(/\s+$/, '');
}
String.prototype.trim = function(clist) {
    if (clist) return this.ltrim (clist).rtrim (clist);
    return this.ltrim ().rtrim ();
}



