/* Firefox Operator plugin to export all of a web page's Geo Data to Google Earth KML format
 * Version 1.0
 * Written by Dan Noble
 * Copyright 2007 The MITRE Corporation. All rights reserved.
 * This code is available under the Creative Commons Attribution License
 * (http://creativecommons.org/licenses/by/3.0/)
 */
 
 if (Components.utils.import) {
  try {
    Components.utils.import("rel:Microformats.js");
    Components.utils.import("rel:hCard.js");
	Components.utils.import("rel:hCalendar.js");
	Components.utils.import("rel:adr.js");
	Components.utils.import("rel:geo.js");
  } catch (ex) {}
}
 
var view_hcard_hcal_ge = {
	version: 0.8,
	description: "View in Google Earth",
	shortDescription: "View in Google Earth",
	descriptionAll: "View All in Google Earth",
	scope: {
		semantic:{
			"hCalendar" : "location.geo",
			"hCard" : "adr"
		}
	},
	doActionAll: function(semanticArrays, semanticObjectType) {
		// Export All function
		
		// Gather all of the hCard data that have either an adr or geo field into one data structure:
		var hCardData = view_hcard_hcal_ge.getHCardsWithAdrOrGeo(semanticArrays["hCard"]);
		
		// Gather all of the hCalendar data that have an adr field into one data structure
		var hCalendarData = view_hcard_hcal_ge.getHCalendarsWithAdr(semanticArrays["hCalendar"]);
		
		// Put data data into KML format:
		var kmlData = "";
		kmlData += view_hcard_hcal_ge.getKMLHeader();
		kmlData += view_hcard_hcal_ge.getKMLFromHCards(hCardData);
		kmlData += view_hcard_hcal_ge.getKMLFromHCalendars(hCalendarData);
		kmlData += view_hcard_hcal_ge.getKMLFooter();
		
		// Remove illegal characters from the kml
		kmlData = view_hcard_hcal_ge.fixXML(kmlData);
		
		// Save the KML file to disk !
		var kmlFileURL = view_hcard_hcal_ge.saveKMLFile(kmlData);
		
		// Open the kml file !
		window.open(kmlFileURL);
		
		return;
    }, 
	doAction: function(semanticObject, semanticObjectType) {
		// Export single item function
		// Create an array of 1 element (that way we can use the same function that "doActionAll" uses
		var data = new Array();
		data[0] = semanticObject;
		
		// Put data data into KML format:
		var kmlData = "";
		kmlData += view_hcard_hcal_ge.getKMLHeader();
		if (semanticObjectType == "hCard") 
			kmlData += view_hcard_hcal_ge.getKMLFromHCards(data);
		else 
			kmlData += view_hcard_hcal_ge.getKMLFromHCalendars(data);
		kmlData += view_hcard_hcal_ge.getKMLFooter();
		
		// Remove illegal characters from the kml
		kmlData = view_hcard_hcal_ge.fixXML(kmlData);
		
		// Save the KML file to disk !
		var kmlFileURL = view_hcard_hcal_ge.saveKMLFile(kmlData);
		
		// Open the kml file !
		window.open(kmlFileURL);
		
		return;
	},
	getHCardsWithAdrOrGeo: function(hCards){
		var hCardsWithAdrOrGeo = new Array();
		
		for(var i in hCards) {
			if (hCards[i].adr){
				hCardsWithAdrOrGeo.push(hCards[i]);
			}
		}
		return hCardsWithAdrOrGeo;
	},
	getHCalendarsWithAdr: function (hCalendars){
		var hCalendarsWithAdr = new Array();
		
		for(var i in hCalendars) {
			if (hCalendars[i].location && hCalendars[i].location.geo){
				hCalendarsWithAdr.push(hCalendars[i]);
			}
		}
		return hCalendarsWithAdr;
	},
	getKMLHeader: function () {
		var kmlHeader="";	
		// Header
		kmlHeader += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
		kmlHeader += "<kml xmlns=\"http://earth.google.com/kml/2.1\">\n";
		kmlHeader += "\t<Document>\n";
		kmlHeader += "\t\t<name>Operator Export</name>\n";
		kmlHeader += "\t\t<open>1</open>\n";
		
		return kmlHeader;
	},
	getKMLFromHCards: function  (data) {
		var kmlData = "";
		// Create a folder for hCards:
		kmlData += "\t\t<Folder>\n";
		kmlData += "\t\t\t<name>hCards</name>\n";
		// Add all data elements
		for (var i in data)  {
			kmlData += "\t\t\t<Placemark>\n";
			if (data[i].fn) kmlData += "\t\t\t\t<name>"+data[i].fn + "</name>\n";
			
			kmlData += "\t\t\t\t<description> <![CDATA[ ";
			kmlData += view_hcard_hcal_ge.hCardToHTML(data[i]);
			kmlData += " ]]></description>\n"; 
			if (data[i].adr) kmlData += "\t\t\t\t<address>"+ view_hcard_hcal_ge.adrToText(data[i].adr[0]) +"</address>\n";
			if (data[i].geo) {
				kmlData += "\t\t\t\t<Point>\n";
				kmlData += "\t\t\t\t\t<coordinates>"+ view_hcard_hcal_ge.geoToText(data[i].geo)+"</coordinates>\n";
				kmlData += "\t\t\t\t</Point>\n";
			}
			// Icon
			kmlData += "\t\t\t\t<Style>\n";
			kmlData += "\t\t\t\t\t<IconStyle>\n";
			kmlData += "\t\t\t\t\t\t<color>ffffffff</color>\n";
			kmlData += "\t\t\t\t\t\t<scale>1.1</scale>\n";
			kmlData += "\t\t\t\t\t\t<Icon>\n";
			kmlData += "\t\t\t\t\t\t\t<href>http://maps.google.com/mapfiles/kml/pal3/icon48.png</href>\n";
			kmlData += "\t\t\t\t\t\t</Icon>\n";
			kmlData += "\t\t\t\t\t</IconStyle>\n";
			kmlData += "\t\t\t\t</Style>\n";
			kmlData += "\t\t\t</Placemark>\n";
		}
		kmlData += "\t\t</Folder>\n";
		return kmlData;
	},
	getKMLFromHCalendars: function  (data) {
		var kmlData = "";
		
		// We will populate each Google earth Placemark entry with these entries:
		// 1. name - Describes the event
		// 2. description (if one exists) - html description of the event
		// 3. address - Location of the event
		// 4. geo (if one exists) - longitude, latitude and altitude of the event
		kmlData += "\t\t<Folder>\n";
		kmlData += "\t\t\t<name>hCalendar</name>\n";
		
		for (var i in data) {
			kmlData += "\t\t<Placemark>\n";
			// Name 
			if (data[i].summary) kmlData += "\t\t\t<name>"+data[i].summary + "</name>\n";
			// Description
			kmlData += "\t\t\t<description> <![CDATA[ ";
			kmlData += view_hcard_hcal_ge.hCalendarToHTML(data[i]);
			kmlData += " ]]></description>\n"; 
			// Address
			if (data[i].location.adr) kmlData += "\t\t\t<address>"+ view_hcard_hcal_ge.adrToText(data[i].location.adr[0]) +"</address>\n";
			// Geo
			if (data[i].location.geo) {
				kmlData += "\t\t\t<Point>\n";
				kmlData += "\t\t\t\t<coordinates>"+ view_hcard_hcal_ge.geoToText(data[i].location.geo)+"</coordinates>\n";
				kmlData += "\t\t\t</Point>\n";
			}
			// Date
			if (data[i].dtstart) {
				kmlData += "\t\t\t<TimeSpan>\n";
				if (data[i].dtstart){
					kmlData += "\t\t\t\t<begin>" + data[i].dtstart + "</begin>\n";
				}
				if (data[i].dtend){
					kmlData += "\t\t\t\t<end>" + data[i].dtend + "</end>\n";
				}
				kmlData += "\t\t\t</TimeSpan>\n";
			}
			// Icon
			kmlData += "\t\t\t<Style>\n";
			kmlData += "\t\t\t\t<IconStyle>\n";
			kmlData += "\t\t\t\t\t<color>ffffffff</color>\n";
			kmlData += "\t\t\t\t\t<scale>1.1</scale>\n";
			kmlData += "\t\t\t\t\t<Icon>\n";
			kmlData += "\t\t\t\t\t\t<href>http://maps.google.com/mapfiles/kml/pal3/icon36.png</href>\n";
			kmlData += "\t\t\t\t\t</Icon>\n";
			kmlData += "\t\t\t\t</IconStyle>\n";
			kmlData += "\t\t\t</Style>\n";
			kmlData += "\t\t</Placemark>\n";
		}
		
		// Create a line connecting all of the points
		
		var datesToGeo = new Array();
		var datesArray = new Array();
		for (var i in data){
			if (data[i].dtstart && (data[i].location && data[i].location.geo)){
				datesToGeo[data[i].dtstart] = data[i].location.geo;
				datesArray.push(data[i].dtstart);
			}
		}
		
		if (datesArray.length > 1) {
			datesArray = datesArray.sort();
			
			kmlData += "\t\t<Placemark>\n";
				kmlData += "\t\t\t<name>Chronological Path</name>\n";
				kmlData += "\t\t\t<Style>\n";
					kmlData += "\t\t\t\t<LineStyle>\n";
						kmlData += "\t\t\t\t\t<color>ff0000ff</color>\n";
						kmlData += "\t\t\t\t\t<width>2</width>\n";
					kmlData += "\t\t\t\t</LineStyle>\n";
				kmlData += "\t\t\t</Style>\n";
				kmlData += "\t\t\t<LineString>\n";
					kmlData += "\t\t\t\t<tessellate>1</tessellate>\n";
					kmlData += "\t\t\t\t<altitudeMode>clampToGround</altitudeMode>\n";
					kmlData += "\t\t\t\t<coordinates>\n";
						for (var i in datesArray){
							var str = datesToGeo[datesArray[i]].longitude + "," + datesToGeo[datesArray[i]].latitude + ",0 ";
							kmlData += str;
						}
						
					kmlData += "</coordinates>\n";
				kmlData += "\t\t\t</LineString>\n";
			kmlData += "\t\t</Placemark>\n";
		}
		
		kmlData += "\t\t</Folder>\n";
		return kmlData;
	},

	getKMLFooter: function () {
		kmlFooter = "";
		kmlFooter += "\t</Document>\n";
		kmlFooter += "</kml>\n";
		return kmlFooter;
		
	},

	hCardToHTML: function (data) {
		var htmlString = "";
		
		
		// [ ] Logo
		if (data.logo) htmlString += "<img src = \"" + data.logo + "\" /><br />\n";
		
		// Photo and Name
		if (data.photo) htmlString += "<img src = \"" + data.photo + "\" /><br />\n";
		if (data.url) htmlString += "<a href=\""+data.url[0] +"\">";
		if (data.fn) htmlString += data.fn + "<br />\n";
		if (data.url) htmlString += "</a>";
		
		// [ ] Nickname - TODO: Implicit optimization
		if (data.nickname) htmlString += "Nickname - " + data.nickname + "<br />\n";
		
		// [ ] Sort-string?
		if (data["sort-string"]) htmlString += "Sort-string - " + data["sort-string"][0] + "<br />\n";
		
		// [ ] Address
		if (data.adr) htmlString += view_hcard_hcal_ge.adrToHTML(data.adr)+ "\n";
		
		// [ ] Email Address
		if (data.email ) htmlString += view_hcard_hcal_ge.emailToText(data.email)+ "\n";
		
		// [ ] Telephone Number
		if (data.tel) htmlString += view_hcard_hcal_ge.telToText(data.tel)+ "\n";
		
		// [ ] Label
		if (data.label) htmlString += "Label - " + data.label + "<br />\n";
		
		// [ ] Geo
		if (data.geo) htmlString += view_hcard_hcal_ge.geoToText(data.geo) + "<br />\n";
		
		// [ ] Tz
		if (data.tz) htmlString += "Timezone - " + data.tz + "<br />\n";
		
		// [ ] Sound
		if (data.sound) htmlString += "Sound - " + data.sound + "<br />\n";
		
		// [ ] Birthday
		if (data.bday) htmlString += "Born - " + data.bday + "<br />\n";
		
		// [ ] Title
		if (data.title) htmlString += data.title[0] + "<br />\n";
		
		// [ ] Role
		if (data.role) htmlString += "Role - " + data.role[0] + "<br />\n";
		
		// [ ] Org - TODO: Implicit optimization
		if (data.org) {
			if (data.org[0]["organization-name"]) {
				// Optimization - Only display the organization if it is not the same as the 'fn'
				if (data.fn && data.fn != data.org[0]["organization-name"])
					htmlString +=  "Organization: " + data.org[0]["organization-name"]+ "<br />\n";
			}
			if (data.org[0]["organization-unit"]) {
				htmlString += "Unit: " + data.org[0]["organization-unit"].join(",")+ "<br />\n";
			}
	    }
		
		//[ ] Category - Added: Join
		if (data.category) htmlString += "Category - " + data.category.join(",") + "<br />\n";
		
		// [ ] Note 
		if (data.note) {
			htmlString += "Note - ";
			for (i=0; i< data.note.length;i++) {
		        var s = data.note[i];
		        s = s.replace(/\<.*?\>/gi, ' ');
		        s = s.replace(/[\n\r\t]/gi, ' ');
		        s = s.replace(/\s{2,}/gi, ' ');
		        s = s.replace(/\s{2,}/gi, '');
		        s = s.replace(/^\s+/, '');
		        if (i != 0) {
					htmlString += " ";
		        }
		        htmlString += s;
			}
			htmlString += "<br />\n";
	    }
		
		// [ ] Class 
		if (data["class"]) htmlString += "Class - " + data["class"] + "<br />\n";
		
		// [ ] Key - Eliminate?
		if (data.key) htmlString += "Key - " + data.key + "<br />\n";
		
		// [ ] Mailer - Eliminate?
		if (data.mailer) htmlString += "Mailer - " + data.mailer + "<br />\n";
		
		// [ ] UID
		if (data.uid) htmlString += "UID - " + data.uid + "<br />\n";
		
		// [ ] Rev
		if (data.rev) htmlString += "Rev - " + data.rev + "<br />\n";

		return htmlString;
	},
	
	hCalendarToHTML: function  (hcalendar) {
		var htmlString = "";
		
		if (hcalendar.url) htmlString += "<a href=\"" + hcalendar.url + "\">";
		if (hcalendar.summary) htmlString += hcalendar.summary + "<br / >\n";
		if (hcalendar.url) htmlString += "</a>\n";
		
		if (hcalendar.dtstart) {
			htmlString += "Start Date: " + hcalendar.dtstart + "<br />";
		}
		
		if (hcalendar.dtend) {
			htmlString += "End Date: " + hcalendar.dtend + "<br />";
		}
		
		if (hcalendar.duration) {
			htmlString += "Duration: " + hcalendar.duration + "<br />";
		}
		
		if (hcalendar.description) {
			htmlString += "Description:<br />";
			htmlString += hcalendar.description;
	    }
		
		if (hcalendar.location) { 
			htmlString += "<br />" + view_hcard_hcal_ge.hCardToHTML(hcalendar.location);
		}
		
		if (hcalendar["last-modified"]){
			htmlString += "Last Modified: " + hcalendar["last-modified"];
		}
		
		return htmlString;
	},

	adrToHTML: function (adr) {
		var textString ="";

		// Error checking
		if (!adr) return;

		for (var i in adr) {
			if (adr[i].type) {
				textString += adr[i].type.join(",") + ": " ;
				textString += " ";
			}
			textString += view_hcard_hcal_ge.adrToText(adr[i]);
			textString += "<br />\n";
		}
	    
		return textString;
	},
	
	adrToText: function (adr) {
		var textString = "";

		if (adr["post-office-box"]) {
			textString += adr["post-office-box"];
			textString += " ";
		}
		if (adr["street-address"]) {
			textString += adr["street-address"].join(",");
		}
		if (adr.locality) {
			if (adr["street-address"]) textString += ",";
			textString += " ";
			textString += adr.locality;
		}
		if (adr.region) {
			textString += ", ";
			textString += adr.region;
		}
		if (adr["postal-code"]) {
			textString += " ";
			textString += adr["postal-code"];
		}
		if (adr["country-name"]) {
			if (adr["postal-code"]) textString += ",";
			textString += " ";
			textString += adr["country-name"];
		}
		
		return textString;
	},

	geoToText: function (geo){
		var textString = "";
		
		// Error checking
		if (!geo) return;
		
		// Determine if there is an altitude
		var altitude;
		if (geo.altitude)
			altitude = geo.altitude;
		else
			altitude = 0;
			
		// Build geo text string
		var textString = geo.longitude + "," + geo.latitude + ","  + altitude;
		return textString;
	},

	emailToText: function (email){
		
		var textString = "";

		if (!email) return;
		
		for (var i in email) {
			if (email[i].type) {
				textString += email[i].type.join(",");
				textString +=  ":";
			}
			if (email[i].value)
				textString += email[i].value;
			else 
				textString += email[i];
			textString += "<br />\n";
		}
	    return textString;
	},

	telToText: function (tel){
		var htmlString = "";
		if (tel) {
	      for (var i in tel) {
	        if (tel[i].type) {
	          htmlString += tel[i].type.join(",");
			  htmlString +=  ":";
	        }
	        htmlString += tel[i].value + "<br />\n";
	      }
	    }
		return htmlString;
	},

	saveKMLFile: function (kmlData) {
		//initialize the file for the data
		var file = Components.classes["@mozilla.org/file/directory_service;1"]
				 .getService(Components.interfaces.nsIProperties)
				 .get("TmpD", Components.interfaces.nsIFile);
		file.append("GoogleEarth_Export.kml");
		file.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0664);
		// file is nsIFile
		var ios = Components.classes["@mozilla.org/network/io-service;1"]
				.getService(Components.interfaces.nsIIOService);
		var fileHandler = ios.getProtocolHandler("file")
				 .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
		var kmlFileURL = fileHandler.getURLSpecFromFile(file);
		var fos = Components.classes["@mozilla.org/network/file-output-stream;1"].
						   createInstance(Components.interfaces.nsIFileOutputStream);

		fos.init(file, 0x04 | 0x08 | 0x20, 420, 0);
		fos.write(kmlData, kmlData.length);
		fos.close();
		return kmlFileURL;
	},

	fixXML: function (xml) {
		xml = xml.replace(/\&/gi, '&amp;');
		return xml;
	}
};
SemanticActions.add("view_hcard_hcal_ge", view_hcard_hcal_ge);