save.js

Summary

Tools for saving content using Mozile. WARNING: It is your responsibility to verify the safety of data that Mozile posts to your server! Failure to do so could lead to a cross site scripting vulnerability.

Project Homepage: http://mozile.mozdev.org

Version: 0.8 $Id: overview-summary-save.js.html,v 1.8 2008/02/20 18:47:09 jameso Exp $

Author: James A. Overton


Class Summary
mozile.save.Method  

/* ***** BEGIN LICENSE BLOCK *****
 * Licensed under Version: MPL 1.1/GPL 2.0/LGPL 2.1
 * Full Terms at http://mozile.mozdev.org/0.8/LICENSE
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is James A. Overton's code (james@overton.ca).
 *
 * The Initial Developer of the Original Code is James A. Overton.
 * Portions created by the Initial Developer are Copyright (C) 2005-2006
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *	James A. Overton <james@overton.ca>
 *
 * ***** END LICENSE BLOCK ***** */

/**
 * @fileoverview Tools for saving content using Mozile.
 * WARNING: It is your responsibility to verify the safety of data that Mozile posts to your server! Failure to do so could lead to a cross site scripting vulnerability.
 * <p>Project Homepage: http://mozile.mozdev.org 
 * @author James A. Overton <james@overton.ca>
 * @version 0.8
 * $Id: overview-summary-save.js.html,v 1.8 2008/02/20 18:47:09 jameso Exp $
 */

mozile.require("mozile.dom");
mozile.require("mozile.xml");
mozile.provide("mozile.save.*");

/**
 * Tools for saving Mozile results.
 * @type Object
 */
mozile.save = new Object();
// JSDoc hack
mozile.save.prototype = new mozile.Module;

/**
 * Determines which save method will be used to save the document.
 * @type Object
 */
mozile.save.method = null;

/**
 * Determines the node to save.
 * @type Node
 */
mozile.save.target = mozile.document;

/**
 * Determines the string format to use for saving.
 * @type String
 */
mozile.save.format = null;

/**
 * Indicates that the user should be warned before leaving a page with unsaved changes.
 * @type Boolean
 */
mozile.save.warn = true;

/**
 * Add a BeforeUnload handler to present a warning if the document hasn't been saved.
 * @type String
 */
mozile.save.onbeforeunload = function(event) {
	if(!mozile.save.warn) return undefined;
	if(!event || !event.target) return undefined;
	var win = event.target;
	if(mozile.save.isSaved(win)) return undefined;
	return "There are unsaved changes in this document. Changes will be lost if you navigate away from this page.";
}

/**
 * Determines whether the document has been saved in its current state.
 * @param {Document} doc Optional. The target document.
 * @type Boolean
 * @return true if no changes have been made since the document was last saved.
 */
mozile.save.isSaved = function(doc) {
	if(!doc) doc = mozile.document;
	// No changes can be made without the edit module.
	if(!mozile.edit) return true;
	// If the current state is null, no changes have been made.
	var currentState = mozile.edit.getCurrentState(doc);
	if(!currentState) return true;
	// Otherwise, compare the currentState to the savedState.
	if(currentState != this.getSavedState(doc)) return false;
	return true;
}

/**
 * Get the State that the document was in when it was last saved.
 * @param {Document} doc Optional. The target document.
 * @type State
 */
mozile.save.getSavedState = function(doc) {
	if(!doc) doc = mozile.document;
	return doc.savedState;
}

/**
 * Saves the document using the current mozile.save.method.save().
 * @type Boolean
 * @return True if the save operation was successful.
 */
mozile.save.save = function() {
	if(!mozile.save.method) return false;
	var content = mozile.save.getContent(mozile.save.target, mozile.save.format);
	var result = mozile.save.method.save(content);
	if(result && mozile.edit) mozile.document.savedState = mozile.edit.getCurrentState();
	return result;
}

/**
 * Saves the document using the current mozile.save.method.saveAs().
 * @type Boolean
 * @return True if the save operation was successful.
 */
mozile.save.saveAs = function() {
	if(!mozile.save.method) return false;
	var content = mozile.save.getContent(mozile.save.target, mozile.save.format);
	var result = mozile.save.method.saveAs(content);
	if(result && mozile.edit) mozile.document.savedState = mozile.edit.getCurrentState();
	return result;
}

/**
 * Gets the content of a document or a node as a string.
 * @param {Node} target Optional. Get the content of this node. If no node is given the documentElement is used.
 * @param {String} format Optional. The format of the string. Can be "uppercase" or "lowercase". If none is given no formatting is done.
 * @type String
 */
mozile.save.getContent = function(target, format) {
	var content = "";
	if(!target) target = mozile.document;
	if(target.nodeType == 9) { // DOCUMENT_NODE
		target = target.documentElement;
		content += mozile.save.getXMLDeclaration();
		content += mozile.save.getDoctypeDeclaration();
		content += mozile.save.getProcessingInstructions();
	}

// Ignore IE stupid errors.
try {

	// Make a copy of the target and clean it.
	target = target.cloneNode(true);
	target = mozile.save.cleanDOM(target);

} catch(e) { }

	content += mozile.xml.serialize(target);
	if(format) {
		if(format.toLowerCase() == "uppercase")
			content = mozile.save.toUpperCase(content);
		else if(format.toLowerCase() == "lowercase")
			content = mozile.save.toLowerCase(content);
	}
	content = mozile.save.cleanContent(content);
	
	return content;
}

/** 
 * The XML declaration for the document, if there is one.
 * @type String
 */
mozile.save.getXMLDeclaration = function() {
	var xmlDeclaration = "";
	if(mozile.document.xmlVersion) {
		xmlDeclaration = '<?xml version="'+ mozile.document.xmlVersion +'" encoding="'+ mozile.document.xmlEncoding +'"?>\n'
	}
	return xmlDeclaration;
}

/** 
 * The DOCTYPE declaration for the document, if there is one.
 * @type String
 */
mozile.save.getDoctypeDeclaration = function() {
	var doctypeDeclaration = "";
	if(mozile.document.doctype) {
		doctypeDeclaration = mozile.xml.serialize(document.doctype) +"\n";
	}
	return doctypeDeclaration;
}

/** 
 * The Processing Instructions for the document, if there are any.
 * @type String
 */
mozile.save.getProcessingInstructions = function() {
	var PIString = "";
	if(mozile.window.XPathEvaluator) {
		var	evaluator =	new	XPathEvaluator();
		var PIList = evaluator.evaluate("/processing-instruction()", mozile.document, null, XPathResult.ANY_TYPE, null);
		var PI = PIList.iterateNext();
		while (PI) {
			PIString += "<?"+ PI.target +" "+ PI.data + "?>\n";
			PI = PIList.iterateNext();
		}
	}
	return PIString;
}

/**
 * Cleans the DOM tree of a node, removing any changes that Mozile has made.
 * @param {Node} target The node to clean.
 * @type String
 */
mozile.save.cleanDOM = function(target) {
	var root = target;
	if(root.nodeType == mozile.dom.DOCUMENT_NODE)
		root = root.documentElement;

	// Clean up after the extension.
	if(root.nodeType == mozile.dom.ELEMENT_NODE &&
		root.hasAttribute("mozileStatus")) {
		root.removeAttribute("mozileStatus");
		root.removeAttribute("mozileEnhancements");
		root.removeAttribute("mozileClientRequest");
		root.removeAttribute("mozileClientResponse");
		root.removeAttribute("mozileServerRequest");
		root.removeAttribute("mozileServerResponse");
		for(var i=0; i < root.childNodes.length; i++) {
			if(root.childNodes[i].nodeName.toLowerCase() == "body") {
				root.childNodes[i].removeAttribute("contentEditable");
				break;
			}
		}
	}

	if(document.createTreeWalker && mozile.dom.NodeFilter) {
		var treeWalker = document.createTreeWalker(target, mozile.dom.NodeFilter.SHOW_ALL, null, false);
		treeWalker.currentNode = target;
		var current = treeWalker.currentNode;
		var remove = new Array();
		
		// Mark nodes for removal.
		while(current) {
			switch(mozile.dom.getClass(current)) {
				case "mozileLink": // Links added by mozile.dom.addStyleSheet
				case "mozileGUI": // Elements added by the GUI
				case "mozileScript": // Scripts added by the extension
				case "mozileSchema": // Schemas added by the extension
					remove.push(current);
					break;
			}
			
			// TODO: Clean up changes to contentEdtiable attributes.
			//if(current.mozile) alert("Mozile found "+ current.nodeName);
			
			current = treeWalker.nextNode();
		}

		// Remove nodes.
		while(remove.length) {
			if(remove[0].parentNode) remove[0].parentNode.removeChild(remove[0]);
			remove.shift();
		}
		
	}
	else mozile.debug.inform("mozile.save.cleanDOM", "Could not clean target because no TreeWalker is available.");

	return target;
}

/**
 * Cleans the XML content string from getContent(), removing any changes that Mozile has made.
 * @param {String} content The string to clean.
 * @type String
 */
mozile.save.cleanContent = function(content) {
	return content;
}

/**
 * Converts &lt; and &gt; characters.
 * @param {String} content The string to clean.
 * @type String
 */
mozile.save.cleanMarkup = function(content) {
	content = content.replace(/</g, "&lt;");
	content = content.replace(/>/g, "&gt;");
	return content;
}

/**
 * A regular expression to match the beginning of XML and HTML tags.
 * @private
 * @type RegExp
 */
mozile.save._tagPattern = /<(\/*)(\w*)/g; /* match tags */;

/**
 * Converts the element names of an XML string to upper case.
 * @param {String} content
 * @type String
 */
mozile.save.toUpperCase = function(content) {
	return content.replace(mozile.save._tagPattern, function(word) { return word.toUpperCase(); });
}

/**
 * Converts the element names of an XML string to lower case.
 * @param {String} content
 * @type String
 */
mozile.save.toLowerCase = function(content) {
	return content.replace(mozile.save._tagPattern, function(word) { return word.toLowerCase(); });
}

/**
 * Class for save methods.
 * @constructor
 * @param {String} name A name for the save method.
 */
mozile.save.Method = function(name) {
	/**
	 * The name of the save method.
	 * @type String
	 */
	this.name = name;
}

/**
 * An abstract save method.
 * @param {String} content The content to save.
 * @type Boolean
 */
mozile.save.Method.prototype.save = function(content) {
	return false;
}

/**
 * An abstract saveAs method.
 * @param {String} content The content to save.
 * @type Boolean
 */
mozile.save.Method.prototype.saveAs = function(content) {
	return this.save(content);
}


/**** Display Source Code ****/

/**
 * Display the content in a new window.
 * @type mozile.save.Method
 */
mozile.save.display = new mozile.save.Method("Display Source");

/**
 * @param {String} content The content to save.
 * @type Boolean
 */
mozile.save.display.save = function(content) {
	content = mozile.save.cleanMarkup(content);
	if(mozile.gui) {
		mozile.gui.display('<h3>Mozile Source</h3>\n<pre>'+ content +'</pre>');
	}
	else alert("Mozile Source\n\n"+ content);
	return true;
}

// Set the default save method.
mozile.save.method = mozile.save.display;



/**** Save Via POST ****/

/**
 * Save the content using HTTP POST.
 * @type mozile.save.Method
 */
mozile.save.post = new mozile.save.Method("POST");

/**
 * Indicates an asynchronous request.
 * @type Boolean
 */
mozile.save.post.async = true;

/**
 * Indicates that the response to the save request should be displayed.
 * @type Boolean
 */
mozile.save.post.showResponse = false;

/**
 * The URI to post to.
 * @type String
 */
mozile.save.post.uri = "";

/**
 * The user for the post operation.
 * @type String
 */
mozile.save.post.user = null;

/**
 * The password for the post operation.
 * @type String
 */
mozile.save.post.password = null;

/**
 * The contentType to use.
 * @type String
 */
mozile.save.post.contentType = "text/html";
if(mozile.document.contentType) mozile.save.post.contentType = mozile.document.contentType;

/**
 * The character set to use.
 * @type String
 */
mozile.save.post.characterSet = "UTF-8";
if(mozile.document.characterSet) mozile.save.post.characterSet = mozile.document.characterSet;

/**
 * Save using HTTP POST and the XMLHttpRequest object.
 * @param {String} content The content to save.
 * @type Boolean
 */
mozile.save.post.save = function(content) {
	if(!this.uri) {
		if(mozile.debug) mozile.debug.inform("mozile.save.post.save", "No URI to save to.");
		return false;
	}
	
	var CR = '\x0D';
	var LF = '\x0A';
	content = CR + LF + content + CR + LF;

	if(this.XHR) this.XHR.abort();
	this.XHR = null;
	var XHR;
	try{
		if(mozile.window.XMLHttpRequest) {
			XHR = new XMLHttpRequest();
		}
		else if(mozile.window.ActiveXObject) {
			XHR = new ActiveXObject('Microsoft.XMLHTTP');
		}
	} catch(e) { 
		if(mozile.debug) mozile.debug.inform("mozile.save.post.save", "File save failed for '"+ this.uri +"' with error message:\n"+ e);
		return false;
	}
	
	if(XHR) {	
		XHR.open("POST", this.uri, this.async, this.user, this.password);
		XHR.setRequestHeader('Content-Type', this.contentType + "; " + this.characterSet);	
		if(mozile.browser.mozile && mozile.browser.mozileVersion < 1.8) 
			XHR.setRequestHeader('Content-Length', content.length);
		XHR.setRequestHeader('Content-Location', this.uri);
		XHR.onreadystatechange = this.onreadystatechange;
		XHR.send(content);
		
		this.XHR = XHR;
		if(!this.async) {
			this.onreadystatechange();
		}
		return true;
	}
	
	if(mozile.debug) mozile.debug.inform("mozile.save.post.save", "No XMLHttpRequest available when trying to save to '"+ this.uri +"'.");
	return false;
}

/**
 * Handler for an asynchronous request.
 * @type Void
 */
mozile.save.post.onreadystatechange = function() {
	var XHR = mozile.save.post.XHR;
	if(!XHR) return;
	// If the request is not complete, ignore this change.
	if(XHR.readyState != 4) return;
	
	if(XHR.status == 0 || XHR.status == 200) {
		if(mozile.save.post.showResponse) 
			mozile.gui.display('<h3>Save Operation Response</h3>\n\n'+ XHR.responseText);
	}
	else {
		if(mozile.save.post.showResponse) 
			mozile.gui.display('<h3>Save Operation Error</h3>\n\n'+ XHR.responseText);
		else if(mozile.debug) mozile.debug.inform("mozile.save.post.save", "File save failed with status '"+ XHR.status +"' and message:\n"+ XHR.responseText);
	}
}

/**
 * Set the URI before saving.
 * @param {String} content The content to save.
 * @type Boolean
 */
mozile.save.post.saveAs = function(content) {
	var uri = prompt("Save to what URI?", this.uri);
	if(!uri) return false;
	this.uri = uri;
	return this.save(content);
}




Documentation generated by JSDoc on Wed Feb 20 13:25:28 2008