XHTMLBasic.js

Summary

The XHTMLBasic modules includes commands derived from the MozileCommand object which allow for editing of HTML and XHTML documents. ALso included is a revision of the Mozile.initializeToolbar() function, which creates a number of commands commonly used in XHTML editing. This module does not include support for validation of XHTML documents. Instead it makes some assumptions about XHTML, which lead to a more light-weight implementation. Elements are distinguished into block and non-block (inline) elements, based on their CSS "display" property. Most commands function differently on blocks than they do on inline elements. While the distinction could allow the creation of invalid XHTML documents, in most cases the commands will preserve the validity of the XHTML. Module options: - semantic=[true|false] Default is "true". When this option is set to "true", "strong" and "em" will be used for the bold and italic commands. When it is false, "b" and "i" will be used instead.

Version: 0.7.0

Author: James A. Overton


Class Summary
MozileStyleCommand
MozileUnformatCommand
MozileWrapCommand
MozileBlockSetCommand
MozileInsertCommand

/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * 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
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */
 

/** XHTML Basic  
 * @fileoverview The XHTMLBasic modules includes commands derived from the MozileCommand object which allow for editing of HTML and XHTML documents. ALso included is a revision of the Mozile.initializeToolbar() function, which creates a number of commands commonly used in XHTML editing. 
 * This module does not include support for validation of XHTML documents. Instead it makes some assumptions about XHTML, which lead to a more light-weight implementation. Elements are distinguished into block and non-block (inline) elements, based on their CSS "display" property. Most commands function differently on blocks than they do on inline elements. While the distinction could allow the creation of invalid XHTML documents, in most cases the commands will preserve the validity of the XHTML.
 * Module options:
 * - semantic=[true|false] Default is "true". When this option is set to "true", "strong" and "em" will be used for the bold and italic commands. When it is false, "b" and "i" will be used instead.
 * 
 * @link http://mozile.mozdev.org 
 * @author James A. Overton <james@overton.ca>
 * @version 0.7.0
 */


/** Mozile - Initialize Toolbar -
 * Does some basic setup on the toolbar, which can't be done until after createToolbar() is run. It adds a number of standard XHTML editing this.rootCommandList.
 * 
 * @return True if successful.
 */
Mozile.prototype.initializeToolbar = function() {
	var f = new Array();
	f["File"] = "XHTMLBasic/XHTMLBasic.js";
	f["Function"] = "Mozile.initializeToolbar()";
	this.debug(f,1,"Initializing toolbar");

	var location;

	// Add XHTML commands.

	// Bold, Italic, Underline, Superscript, Subscript, Unformat

		// Non-semantic mode: use "b" and "i".
	if(this.moduleList["XHTMLBasic"] && this.moduleList["XHTMLBasic"]["semantic"] && this.moduleList["XHTMLBasic"]["semantic"] == "false") {
		this.rootCommandList.createCommand("MozileWrapCommand: id=Mozile-XHTMLBasic-Strong, tag=b, mode=toggle, label=Bold, tooltip='Make text bold', accelerator='Command-B', image='"+this.root+"images/bold.png'");
		this.rootCommandList.createCommand("MozileWrapCommand: id=Mozile-XHTMLBasic-Emphasis, tag=i, mode=toggle, label=Italic, tooltip='Italicize text', accelerator='Command-I', image='"+this.root+"images/italic.png'");
	}
		// Semantic mode: use "strong" and "em" instead of "b" and "i".
	else {
		this.rootCommandList.createCommand("MozileWrapCommand: id=Mozile-XHTMLBasic-Strong, tag=strong, mode=toggle, label=Strong, tooltip='Make text strong', accelerator='Command-B', image='"+this.root+"images/bold.png'");
		this.rootCommandList.createCommand("MozileWrapCommand: id=Mozile-XHTMLBasic-Emphasis, tag=em, mode=toggle, label=Emphasis, tooltip='Emphasize text', accelerator='Command-I', image='"+this.root+"images/italic.png'");	
	}

	this.rootCommandList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Underline, style='text-decoration: underline', mode=toggle, label=Underline, tooltip='Underline text', accelerator='Command-U', image='"+this.root+"images/underline.png'");
	this.rootCommandList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Strikethrough, style='text-decoration: line-through', mode=toggle, label=Strikethrough, tooltip='Strikethough text',image='"+this.root+"images/strikethrough.png'");
	this.rootCommandList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Superscript, style='vertical-align: super; font-size: 80%', mode=toggle, label=Superscript, tooltip='Raise text',image='"+this.root+"images/superscript.png'");
	this.rootCommandList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Subscript, style='vertical-align: sub; font-size: 80%', mode=toggle, label=Subscript, tooltip='Lower text',image='"+this.root+"images/subscript.png'");
	this.rootCommandList.createCommand("MozileUnformatCommand: id=Mozile-XHTMLBasic-Unformat, label=Unformat, tooltip='Remove formatting from selection', image='"+this.root+"images/unlink.png'");
	
	
	// Font Menu
	var fontList = this.rootCommandList.createCommand("MozileCommandList: id=Mozile-XHTMLBasic-FontList, label=Font, image='"+this.root+"images/fonts.png'");
	fontList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Serif, style='font-family: serif', mode=toggle, label=Serif, tooltip='Use serif font'");
	fontList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Sans-Serif, style='font-family: sans-serif', mode=toggle, label=Sans-Serif, tooltip='Use sans-serif font'");
	fontList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Monospace, style='font-family: monospace', mode=toggle, label=Monospace, tooltip='Use monospace font'");
	
	
	// Size Menu
	var sizeList = this.rootCommandList.createCommand("MozileCommandList: id=Mozile-XHTMLBasic-SizeList, label=Size, image='"+this.root+"images/size.png'");
	sizeList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Smaller, style='font-size: smaller', mode=inline, label=Smaller, tooltip='Make text smaller than surrounding text', accelerator='Command--'");
	sizeList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-XX-Small, style='font-size: xx-small', mode=toggle, label=XX-Small, tooltip='Make text extremely small'");
	sizeList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-X-Small, style='font-size: x-small', mode=toggle, label=X-Small, tooltip='Make text very small'");
	sizeList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Small, style='font-size: small', mode=toggle, label=Small, tooltip='Make text small'");
	sizeList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Medium, style='font-size: medium', mode=toggle, label=Medium, tooltip='Make text medium sized'");
	sizeList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Large, style='font-size: large', mode=toggle, label=Large, tooltip='Make text large'");
	sizeList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-X-Large, style='font-size: x-large', mode=toggle, label=X-Large, tooltip='Make text very large'");
	sizeList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-XX-Large, style='font-size: xx-large', mode=toggle, label=XX-Large, tooltip='Make text extremely large'");
	sizeList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Larger, style='font-size: larger', mode=inline, label=Larger, tooltip='Make text larger than surrounding text', accelerator='Command-+'");


	// Format Menu
	var formatList = this.rootCommandList.createCommand("MozileCommandList: id=Mozile-XHTMLBasic-FormatList, label='Format', image='"+this.root+"images/format.png'");
	formatList.createCommand("MozileBlockSetCommand: id=Mozile-XHTMLBasic-Heading1, tag=h1, label='Heading1', tooltip='Level 1 Heading', accesskey=1");
	formatList.createCommand("MozileBlockSetCommand: id=Mozile-XHTMLBasic-Heading2, tag=h2, label='Heading2', tooltip='Level 2 Heading', accesskey=2");
	formatList.createCommand("MozileBlockSetCommand: id=Mozile-XHTMLBasic-Heading3, tag=h3, label='Heading3', tooltip='Level 3 Heading', accesskey=3");
	formatList.createCommand("MozileBlockSetCommand: id=Mozile-XHTMLBasic-Heading4, tag=h4, label='Heading4', tooltip='Level 4 Heading', accesskey=4");
	formatList.createCommand("MozileBlockSetCommand: id=Mozile-XHTMLBasic-Heading5, tag=h5, label='Heading5', tooltip='Level 5 Heading', accesskey=5");
	formatList.createCommand("MozileBlockSetCommand: id=Mozile-XHTMLBasic-Heading6, tag=h6, label='Heading6', tooltip='Level 6 Heading', accesskey=6");
	formatList.createCommand("MozileBlockSetCommand: id=Mozile-XHTMLBasic-Paragraph, tag=p, label='Paragraph', tooltip='Paragraph', accesskey=P");
	formatList.createCommand("MozileBlockSetCommand: id=Mozile-XHTMLBasic-ListItem, tag=li, label='List Item', tooltip='List Item', accesskey=L");

	
	// Justify Menu
	var justifyList = this.rootCommandList.createCommand("MozileCommandList: id=Mozile-XHTMLBasic-JustifyList, label=Justify, image='"+this.root+"images/justify-left.png'");
	justifyList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Justify-Left, style='text-align: left', mode=toggleBlock, label=Left, tooltip='Justify text left'");
	justifyList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Justify-Right, style='text-align: right', mode=toggleBlock, label=Right, tooltip='Justify text right'");
	justifyList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Justify-Conter, style='text-align: center', mode=toggleBlock, label=Center, tooltip='Center text'");
	justifyList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Justify-Full, style='text-align: justify', mode=toggleBlock, label=Full, tooltip='Justify left and right'");


	// Text Colors
	// red, blue, green, yellow, black, white, purple, orange, gray
	var textColorList = this.rootCommandList.createCommand("MozileCommandList: id=Mozile-XHTMLBasic-TextColorList, label='Text Color', image='"+this.root+"images/text-color.png'");
	textColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Red, style='color: red', mode=toggle, label=Red, tooltip='Make text red' ");
	textColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Blue, style='color: blue', mode=toggle, label=Blue, tooltip='Make text blue' ");
	textColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Green, style='color: green', mode=toggle, label=Green, tooltip='Make text green' ");
	textColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Yellow, style='color: yellow', mode=toggle, label=Yellow, tooltip='Make text yellow' ");
	textColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Orange, style='color: orange', mode=toggle, label=Orange, tooltip='Make text orange' ");
	textColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Purple, style='color: purple', mode=toggle, label=Purple, tooltip='Make text purple' ");
	textColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-White, style='color: white', mode=toggle, label=White, tooltip='Make text white' ");
	textColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Gray, style='color: gray', mode=toggle, label=Gray, tooltip='Make text gray' ");
	textColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Black, style='color: black', mode=toggle, label=Black, tooltip='Make text black' ");

	
	// Background Colors
	// red, blue, green, yellow, black, white, purple, orange, gray
	var backgroundColorList = this.rootCommandList.createCommand("MozileCommandList: id=Mozile-XHTMLBasic-BackgroundColorList, label='Background Color', image='"+this.root+"images/background-color.png'");
	backgroundColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-BGRed, style='background-color: red', mode=toggle, label=Red, tooltip='Make background red' ");
	backgroundColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-BGBlue, style='background-color: blue', mode=toggle, label=Blue, tooltip='Make background blue' ");
	backgroundColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-BGGreen, style='background-color: green', mode=toggle, label=Green, tooltip='Make background green' ");
	backgroundColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-BGYellow, style='background-color: yellow', mode=toggle, label=Yellow, tooltip='Make background yellow' ");
	backgroundColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-BGOrange, style='background-color: orange', mode=toggle, label=Orange, tooltip='Make background orange' ");
	backgroundColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-BGPurple, style='cobackground-colorlor: purple', mode=toggle, label=Purple, tooltip='Make background purple' ");
	backgroundColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-BGWhite, style='background-color: white', mode=toggle, label=White, tooltip='Make background white' ");
	backgroundColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-BGGray, style='background-color: gray', mode=toggle, label=Gray, tooltip='Make background gray' ");
	backgroundColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-BGBlack, style='background-color: black', mode=toggle, label=Black, tooltip='Make background black' ");
	
	
	// Object Insertion
	var objectList = this.rootCommandList.createCommand("MozileCommandList: id=Mozile-XHTMLBasic-ObjectList, label='Insert Objects', image='"+this.root+"images/image.png'");
	// Insert a horizontal rule
	var hr = objectList.createCommand("MozileInsertCommand: id=Mozile-XHTMLBasic-HorizontalRule, label='Horizontal Rule', tooltip='Insert a horizontal rule' ");
	hr.createNode = function() {
		return document.createElement("hr");
	}
	// Insert a link
	var link = objectList.createCommand("MozileInsertCommand: id=Mozile-XHTMLBasic-Link, label='Link', tooltip='Insert a hyperlink' ");
	link.createNode = function() {
		var node = document.createElement("a");
		var href = prompt("What is the URL for the hyperlink?");
		if(href) {
			node.setAttribute("href", href);
			node.appendChild(window.getSelection().getRangeAt(0).extractContents());
			return node;
		} 
		else {
			return false;
		}
	}
	// Insert an image
	var image = objectList.createCommand("MozileInsertCommand: id=Mozile-XHTMLBasic-Image, label='Image', tooltip='Insert an image' ");
	image.createNode = function() {
		var node = document.createElement("img");
		var src = prompt("What is the URL for the image?");
		if(src) {
			node.setAttribute("src", src);
			return node;
		} 
		else {
			return false;
		}
	}
	// Insert an unordered list
	var ul = objectList.createCommand("MozileCommand: id=Mozile-XHTMLBasic-UnorderedList, label='Unordered List', tooltip='Insert an unordered list' ");
	ul.command = function(event) {
		var ul = document.createElement("ul");
		var li = document.createElement("li");
		var textNode = document.createTextNode("");
		li.appendChild(textNode);
		ul.appendChild(li);
		var selection = window.getSelection();
		var range = selection.getRangeAt(0).cloneRange();
		range.insertNode(ul);
		range.selectNode(textNode);
		range.collapse(true);
		selection.removeAllRanges();
		selection.addRange(range);
		return true;
	}
	// Insert an ordered list
	var ol = objectList.createCommand("MozileCommand: id=Mozile-XHTMLBasic-OrderedList, label='Ordered List', tooltip='Insert an ordered list' ");
	ol.command = function(event) {
		var ol = document.createElement("ol");
		var li = document.createElement("li");
		var textNode = document.createTextNode("");
		li.appendChild(textNode);
		ol.appendChild(li);
		var selection = window.getSelection();
		var range = selection.getRangeAt(0).cloneRange();
		range.insertNode(ol);
		range.selectNode(textNode);
		range.collapse(true);
		selection.removeAllRanges();
		selection.addRange(range);
		return true;
	}
	// Change the document title.
	var title = objectList.createCommand("MozileInsertCommand: id=Mozile-XHTMLBasic-Title, label='Change Title', tooltip='Change the document title' ");
	title.command = function(event) {
		var title = document.getElementsByTagName("title")[0];
		var value = prompt("What should the title be?", document.title);
		if(title && value) {
			while(title.childNodes.length) {
				title.removeChild(title.firstChild);
			}
			title.appendChild(document.createTextNode(value));
			document.title = value;
			return true;
		} 
		else {
			return false;
		}
	}
	// Change the style attribute for this element.
	var style = objectList.createCommand("MozileInsertCommand: id=Mozile-XHTMLBasic-Style, label='Change Style', tooltip='Change the element style', accelerator='Command-Alt-S' ");
	style.command = function(event) {
		var selection = window.getSelection();
		var range = selection.getRangeAt(0);
		var container = range.commonAncestorContainer;
		if(container.nodeType==3) container = container.parentNode;
		var value = prompt("What should the style for the "+ container.nodeName +" element be?", container.getAttribute("style"));
		if(value==null) {
			return false;
		} 
		else {
			container.setAttribute("style", value);
			return true;
		}
	}
	// Change some attribute for this element.
	var attribute = objectList.createCommand("MozileInsertCommand: id=Mozile-XHTMLBasic-Attribute, label='Change Attribute', tooltip='Change an attribute of the element', accelerator='Command-Shift-A' ");
	attribute.command = function(event) {
		var selection = window.getSelection();
		var range = selection.getRangeAt(0);
		var container = range.commonAncestorContainer;
		if(container.nodeType==3) container = container.parentNode;
		var attribute = prompt("What attribute of the "+container.nodeName+" element should be changed be?", "");
		if(attribute==null) return false;
		var value = prompt("What should the value of the "+attribute+" attribute be?", container.getAttribute(attribute));
		if(value==null) return false;
		container.setAttribute(attribute, value);
		return true;
	}
	
	// Create the command box.
	this.rootCommandList.createBox();
	
	// Add the rootCommandList.box to the toolbar
	this.toolbar.appendChild(this.rootCommandList.box);
	
	return true;

}








/** Mozile Block Set Command - Object -
 * Sets the properties of the block element which contains the selection. If the selection is collapsed, then the first block-level container for the selection is changed. If the selection is not collapsed (i.e. it contains a range), then that selection is wrapped in a new container.
 * <p>Configuration String Requirements: "tag=[tagName]", for example "tag=h1".
 * @constructor
 *
 * @param configArray An array of configuration information and options, created from a fongiuration string by the Mozile.parseConfig() method.
 * @return True if successful, an error message otherwise.
 */
MozileBlockSetCommand.prototype = new MozileCommand();
MozileBlockSetCommand.prototype.constructor = MozileBlockSetCommand;
MozileBlockSetCommand.superclass = MozileCommand.prototype;

function MozileBlockSetCommand() {

	// If any arguments are passed to this command, then initialize it. Otherwise return.
	if(arguments.length > 0){
		var configArray = arguments[0];
		this.init(configArray);
	}
	else return true;


	/**** DEFINITIONS ****/
	// Define the properties for this object.

	/** Mozile Block Set Command - Tag -
	 * The name of the element which will be created by this command.
	 */
	this.tag = null;


	/**** VALIDATION ****/
	// Check to make sure that values are valid, then set them.
	
	// Check to see if a valid id was provided.
	if(configArray['tag'] && typeof(configArray['tag'])!="null") {
		this.tag = configArray['tag'];
	}
	else {
		return "Error initializing MozileBlockSetCommand object -- invalid tag provided: "+configArray['tag'];
	}

	return true;
}



/** Mozile Block Set Command - Create Menuitem -
 * Creates an XUL menuitem element for this command.
 * 
 * @return The new menuitem element.
 */
MozileBlockSetCommand.prototype.createMenuitem = function() {
	var f = new Array();
	f["File"] = "XHTMLBasic/XHTMLBasic.js";
	f["Function"] = "MozileBlockSetCommand.createMenuitem()";
	this.debug(f,1,"Creating menuitem");
	
	var menuitem = document.createElementNS(XULNS, "menuitem");
	menuitem.setAttribute("id", this.id+"-Menuitem");
	menuitem.setAttribute("class", "mozileMenuitem");
	menuitem.setAttribute("type", "checkbox");
	menuitem.setAttribute("label", this.label);
	if(this.tooltip) menuitem.setAttribute("tooltiptext", this.tooltip);
	if(this.accesskey) menuitem.setAttribute("accesskey", this.accesskey);
	
	this.menuitem = menuitem;
	return menuitem;

}




/** Mozile Block Set Command - Is Active -
 * Checks to see if the parentBlock for the selection matches this.tag. If so, then the command counts as active.
 *
 * @return True of the selection is contained in a tag matching this.tag, false otherwise.
 */
MozileBlockSetCommand.prototype.isActive = function() {
	var f = new Array();
	f["File"] = "XHTMLBasic/XHTMLBasic.js";
	f["Function"] = "MozileBlockSetCommand.isActive()";
	this.debug(f,0,"Checking to see if this command is active");
	
	var selection = window.getSelection();
	var range = selection.getRangeAt(0);
	var node = range.commonAncestorContainer;
	var parentBlock = node.parentBlock;
	if(parentBlock.nodeName.toLowerCase()==this.tag) {
		return true;
	}
	else {
		return false;
	}
}


/** Mozile Block Set Command - Command -
 * If the selection is collapsed, this changes the parentBlock of the current selection to this.tag element. If the seletion is no collapsed, it gets wrapped in a new this.tag element.
 * TODO: Restore the selection properly.
 *
 * @param event The event which triggered this command.
 * @return Always true.
 */
MozileBlockSetCommand.prototype.command = function(event) {
	var f = new Array();
	f["File"] = "XHTMLBasic/XHTMLBasic.js";
	f["Function"] = "MozileBlockSetCommand.command()";
	this.debug(f,1,"Executing command "+event);
	
	var selection = window.getSelection();
	var range = selection.getRangeAt(0).cloneRange();
	
	// If the selection is not collapsed, wrap the selection in the tag and return.
	if(!selection.isCollapsed) {
		var element = document.createElement(this.tag);
		element.appendChild(range.extractContents());
		range.insertNode(element);
		//range.selectNodeContents(element);
		//selection.removeAllRanges();
		//selection.addRange(range);
		return true;
	}
	
	// If the selection is collapsed, then we replace the current block container with a the new tag.
	var node = range.commonAncestorContainer;
	
	// Get the first ancestor element which is a block.
	node = node.parentBlock;
	
	// Select that node, extract the contents, create a new element, append the contents, insert it before the old node, and remove the old node.
	if(node.parentNode) {
		var anchorNode = selection.anchorNode;
		var anchorOffset = selection.anchorOffset;
		var focusNode = selection.focusNode;
		var focusOffset = selection.focusOffset;

		range.selectNodeContents(node);
		var contents = range.extractContents();
		var newNode = document.createElement(this.tag);
		newNode.appendChild(contents);
		node.parentNode.replaceChild(newNode, node);

		// Try to restore the selection.
		range = document.createRange();
		try {
			range.setStart(newNode.firstChild, focusOffset);
		} catch(e) {
			range.setStart(newNode.firstChild, 0);
		}
		selection.removeAllRanges();
		selection.addRange(range);
	}

	return true;

}







/** Mozile Unformat Command Object -
 * This command will remove all formatting from an non-collapsed selection, or remove the wrapper element from around the current node if the selection is collapsed.
 * @constructor
 *
 * @param configArray An array of configuration information and options, created from a fongiuration string by the Mozile.parseConfig() method.
 * @return A string indicating success or an error.
 */
MozileUnformatCommand.prototype = new MozileCommand();
MozileUnformatCommand.prototype.constructor = MozileUnformatCommand;
MozileUnformatCommand.superclass = MozileCommand.prototype;

function MozileUnformatCommand() {

	// If any arguments are passed to this command, then initialize it. Otherwise return.
	if(arguments.length > 0){
		var configArray = arguments[0];
		this.init(configArray);
	}
	else return true;

	return true;
}


/** Mozile Unformat Command - Command -
 * If the selection is collapsed, the parent element is replaced with its own contents (effectively unwrapping the current node). If the selection is not collapsed, then it is converted to a string (without any elements) which then replaces the selection.
 *
 * @param event The event which triggered this command.
 * @return Always true.
 */
MozileUnformatCommand.prototype.command = function(event) {
	var f = new Array();
	f["File"] = "XHTMLBasic/XHTMLBasic.js";
	f["Function"] = "MozileUnformatCommand.command()";
	this.debug(f,1,"Executing command "+event);
	
	var selection = window.getSelection();
	var range = selection.getRangeAt(0).cloneRange();
	var contents,text;
	
	// if the selection is not collapsed, extract the contents as a string and replace the current selection with the string.
	if(!selection.isCollapsed) {
		contents = range.toString();
		text = document.createTextNode(contents);
		range.deleteContents();
		range.insertNode(text);
		
		// Restore the selection.
		range.selectNode(text);
		selection.removeAllRanges();
		selection.addRange(range);
	}
	
	// if the selection is collapsed, remove the element containing the selection, but reinsert all the contents where the element used to be.
	else {	
		var focusOffset = selection.focusOffset;
		var node = range.commonAncestorContainer;
		if(node.nodeType!=1) node = node.parentNode;
		
		range.selectNodeContents(node);
		contents = range.extractContents();
		var firstChild = contents.firstChild;
		range.selectNode(node);
		range.insertNode(contents);

		// Try to restore the selection.
		try {
			range.setStart(firstChild, focusOffset);
		}
		catch(e) {
			range.setStart(firstChild,0);
		}
		range.collapse(true);
		selection.removeAllRanges()
		selection.addRange(range);
	
	}

	return true;

}





/** Mozile Wrap Command Object -
 * Wraps the current selection with an element. This command has three modes: inline, toggle, and block. Inline means that the current selection is wraped in a non-block element. Toggle acts like inline, except that if the current selection is already inside the given element type then the selection "unwrapped" so that it is no longer within that element. Block means that the wrapper element is a block.
 * <p>Configuration String Requirements: "mode=[inline|toggle|block], tag=[tagName]", for example "mode=toggle, tag=b".
 * @constructor
 *
 * @param configArray An array of configuration information and options, created from a fongiuration string by the Mozile.parseConfig() method.
 * @return A string indicating success or an error.
 */
MozileWrapCommand.prototype = new MozileCommand();
MozileWrapCommand.prototype.constructor = MozileWrapCommand;
MozileWrapCommand.superclass = MozileCommand.prototype;

function MozileWrapCommand() {

	// If any arguments are passed to this command, then initialize it. Otherwise return.
	if(arguments.length > 0){
		var configArray = arguments[0];
		this.init(configArray);
	}
	else return true;

	/**** DEFINITIONS ****/
	// Define the properties for this object.

	/** Mozile Wrap Command - Mode -
	 * Selects the way in which this command operates. Can be "inline", "toggle", or "block".
	 */
	this.mode = null;

	/** Mozile Wrap Command - Tag -
	 * The name of the element which will be the wrapper.
	 */
	this.tag = null;
	
	

	/**** VALIDATION ****/
	// Check to make sure that values are valid, then set them.
	
	// Check to see if a valid mode was provided.
	if(configArray['mode'] && typeof(configArray['mode'])!="null") {
		this.mode = configArray['mode'];
	}
	else {
		return "Error initializing MozileWrapCommand object -- invalid mode provided: "+configArray['mode'];
	}
	
	// Check to see if a valid tag was provided.
	if(configArray['tag'] && typeof(configArray['tag'])!="null") {
		this.tag = configArray['tag'];
	}
	else {
		return "Error initializing MozileWrapCommand object -- invalid tag provided: "+configArray['tag'];
	}

	return true;
}



/** Mozile Wrap Command - Create Menuitem -
 * Creates an XUL menuitem element for this command.
 * 
 * @return The new menuitem element.
 */
MozileWrapCommand.prototype.createMenuitem = function() {
	var f = new Array()
	f["File"] = "XHTMLBasic/XHTMLBasic.js";
	f["Function"] = "MozileWrapCommand.createMenuitem()";
	this.debug(f,1,"Creating menuitem");
	
	var menuitem = document.createElementNS(XULNS, "menuitem");
	menuitem.setAttribute("id", this.id+"-Menuitem");
	menuitem.setAttribute("class", "mozileMenuitem");
	menuitem.setAttribute("type", "checkbox");
	menuitem.setAttribute("label", this.label);
	if(this.tooltip) menuitem.setAttribute("tooltiptext", this.tooltip);
	if(this.accesskey) menuitem.setAttribute("accesskey", this.accesskey);
	
	this.menuitem = menuitem;
	return menuitem;

}



/** Mozile Wrap Command - Is Active -
 * Checks to see if the current node has a parent which matches this.tag.
 *
 * @return True of the selection has a parent matching this.tag, false otherwise.
 */
MozileWrapCommand.prototype.isActive = function() {
	var f = new Array();
	f["File"] = "XHTMLBasic/XHTMLBasic.js";
	f["Function"] = "MozileWrapCommand.isActive()";
	this.debug(f,0,"Checking to see if this command is active");

	var selection = window.getSelection();
	var range = selection.getRangeAt(0);
	var node = range.commonAncestorContainer;
	var flag = false;
	while(node) {
		if(node.nodeName.toLowerCase()==this.tag) {
			flag = true;
			break;
		} 
		node = node.parentNode;
	}

	return flag;
}


/** Mozile Wrap Command - Command -
 * Behaves according to the mode. For "inline" it wraps the current selection with this.tag. In the case that the selection spans multiple blocks, it wraps the whole range of the selection. "toggle" behaves like "inline", except that it will unwrap the current selection if it is already wrapped. The "block" mode wraps the selection in a block level element.
 *
 * @param event The event which triggered this command.
 * @return Always true.
 */
MozileWrapCommand.prototype.command = function(event) {
	var f = new Array();
	f["File"] = "XHTMLBasic/XHTMLBasic.js";
	f["Function"] = "MozileWrapCommand.command()";
	this.debug(f,1,"Executing command "+event);
	
	var selection = window.getSelection();
	var range = selection.getRangeAt(0).cloneRange();
	
	var container, element, text, oldRange, blocks, firstElement, lastElement, i, flag;
	

	// Handle the wrap based on the mode
	switch (this.mode) {		// Inline Case
		case "inline":
			// If the selection is collapsed, just add a new wrapper element with and empty text node.
			if(selection.isCollapsed) {
				element = document.createElement(this.tag);
				text = document.createTextNode("");
				element.appendChild(text);
				range.insertNode(element);
				range.selectNode(text);
				range.collapse(true);
				selection.removeAllRanges();
				selection.addRange(range);
			}
			
			// If the selection is not collapsed, wrap the whole range.
			else {
				element = document.createElement(this.tag);
				range = this.wrapRange(range, element)
				selection.removeAllRanges();
				selection.addRange(range);
			}
				
			return true;
			break;


		// Inline Toggle Case
		case "toggle":
			// If the selection is within a case of this.tag, then unwrap it.
			if(this.isActive()) {
				
				// Get the element which makes this selection active.
				container = range.commonAncestorContainer;
				flag = false;
				while(container) {
					if( container.nodeName.toLowerCase() == this.tag ) {
						flag = true;
						break;
					} 
					container = container.parentNode;
				}
				
				element = document.createElement(this.tag);
				range = this.unwrapRange(container, range, element);
				selection.removeAllRanges();
				selection.addRange(range);
			
			}
			
			// Otherwise, behave like "inline"
			else {
		
				// If the selection is collapsed, just add a new wrapper element with and empty text node.
				if(selection.isCollapsed) {
					element = document.createElement(this.tag);
					text = document.createTextNode("");
					element.appendChild(text);
					range.insertNode(element);
					range.selectNode(text);
					range.collapse(true);
					selection.removeAllRanges();
					selection.addRange(range);
				}
				
				// If the selection is not collapsed, wrap the whole range.
				else {
					element = document.createElement(this.tag);
					range = this.wrapRange(range, element);
					selection.removeAllRanges();
					selection.addRange(range);	
				}
			}
				
			return true;
			break;

		// All other cases (particularly "block").
		default:
			element = document.createElement(this.tag);
			element.appendChild(range.extractContents());
			range.insertNode(element);
			if(selection.isCollapsed) {
				text = document.createTextNode("");
				element.appendChild(text);
				range.selectNodeContents(text);
				selection.removeAllRanges();
				selection.addRange(range);
				selection.collapseToEnd();
			}
			else {
				range.selectNodeContents(element);
				selection.removeAllRanges();
				selection.addRange(range);
			}
			return true;	
			break;
	}
	
	return true;
	
}




/** Mozile Wrap Command - Wrap Block -
 * Extract the contents of the given block, and inserts them within the given element which is made to be a child of the block.
 *
 * @param block The block to be wrapped.
 * @param element The element with which to wrap the block.
 * @return The new element.
 */
MozileWrapCommand.prototype.wrapBlock = function(block, element) {
	var f = new Array();
	f["File"] = "XHTMLBasic/XHTMLBasic.js";
	f["Function"] = "MozileWrapCommand.wrapBlock()";
	this.debug(f,1,"Wrapping block "+ block +" with "+ element);
	
	var range = document.createRange();
	if(block.nodeType==1) {
		range.selectNodeContents(block);
	}
	else {
		//alert(block.textContent);
		range.selectNode(block);
	}
	element.appendChild(range.extractContents());
	range.insertNode(element);
	return element;
}



/** Mozile Wrap Command - Wrap Inline -
 * Wraps a range (which does not span blocks) inside the given element.
 *
 * @param range The range to be wrapped.
 * @param element The element with which to wrap the block.
 * @return The new element.
 */
MozileWrapCommand.prototype.wrapInline = function(range, element) {
	var f = new Array();
	f["File"] = "XHTMLBasic/XHTMLBasic.js";
	f["Function"] = "MozileWrapCommand.wrapInline()";
	this.debug(f,1,"Wrapping inline "+ range +" with "+ element);
	
	element.appendChild(range.extractContents());
	range.insertNode(element);
	return element;
}



/** Mozile Wrap Command - Wrap Range -
 * Wraps an entire range (which might span multiple blocks) in copies of the given elementTemplate. This involves calls to wrapBlock and wrapInline.
 *
 * @param range The range to be wrapped.
 * @param elementTemplae The element which will be cloned and used to wrap the block.
 * @return The new range.
 */
MozileWrapCommand.prototype.wrapRange = function(range, elementTemplate) {
	var f = new Array();
	f["File"] = "XHTMLBasic/XHTMLBasic.js";
	f["Function"] = "MozileWrapCommand.wrapRange()";
	this.debug(f,1,"Wrapping range "+ range +" with "+ elementTemplate);
	
	// Get the block in the range.
	var blocks = range.getBlocks();
	var element, firstElement, lastElement;
	
	// If there are no blocks just wrap the selection.
	if(blocks.length<2) {
		element = elementTemplate.cloneNode(true);
		element = this.wrapInline(range, element);
		range.selectNodeContents(element);
	}
	
	// If there are several blocks, then use wrapInline on the first and last, and wrapBlock on all the ones in between
	else {
		// Store the range
		var oldRange = range.cloneRange();
		
		// Wrap to the end of the startContainer.
		range = oldRange.cloneRange();
		if(blocks[0].nodeType==1) {
			range.setEnd(blocks[0].lastChild, blocks[0].lastChild.textContent.length);
		}
		else {
			range.setEnd(blocks[0], blocks[0].textContent.length);
		}
		element = elementTemplate.cloneNode(true);
		firstElement = this.wrapInline(range, element);
		
		// Wrap the contents of all inner blocks.
		for(var i=1; i < blocks.length-1; i++) {
			element = elementTemplate.cloneNode(true);
			this.wrapBlock(blocks[i], element);				
		}
		
		// Wrap from the start of the endContainer.
		range = oldRange.cloneRange();
		range.setStart(blocks[blocks.length-1], 0);
		element = elementTemplate.cloneNode(true);
		lastElement = this.wrapInline(range, element);
	
		// Reset range and restore the selection
		range = document.createRange();
		//range.selectNodeContents(lastElement);
		range.setStart(firstElement.firstChild, 0);
		range.setEnd(lastElement.lastChild, lastElement.lastChild.textContent.length);
	}
	
	return range;
}

/** Mozile Wrap Command - Unwrap Range -
 * Given a range which does not span blocks, remove the range from the container which used to hold it, this removing it from the scope of the this.tag element.
 *
 * @param container The container element with name this.tag from which the range wil be removed.
 * @param range The range to be unwrapped.
 * @param elementTemplate The element which will be cloned and used to wrap the block.
 * @return The new range.
 */
MozileWrapCommand.prototype.unwrapRange = function(container, range, elementTemplate) {
	var f = new Array();
	f["File"] = "XHTMLBasic/XHTMLBasic.js";
	f["Function"] = "MozileWrapCommand.unwrapRange()";
	this.debug(f,1,"Unwrapping range "+ range +" in "+ container +" with "+ element);
	
	// Remember whether the range was collapsed, and store a copy of it.
	var collapsed = range.collapsed;		
	var oldRange = range.cloneRange();
	var element, firstElement, lastElement;
	
	// Wrap part of the container after the range.
	range.selectNodeContents(container);
	range.setStart(oldRange.endContainer, oldRange.endOffset);
	element = elementTemplate.cloneNode(true);
	this.wrapInline(range, element);
	
	// Wrap the part of the container before the range.
	range.selectNodeContents(container);
	range.setEnd(oldRange.startContainer, oldRange.startOffset);
	element = elementTemplate.cloneNode(true);
	this.wrapInline(range, element);
	
	// Remove the container and replace it with its contents.
	range.selectNodeContents(container);
	var contents = range.extractContents();
	firstElement = contents.firstChild;
	lastElement = contents.lastChild;
	// this method avoids leaving empty elements, as opposed to: range.selectNode(container); range.insertNode(contents);
	container.parentNode.replaceChild(contents, container);
	
	// Restore the selection. If the selection was collapsed, insert a new text node between the firstElement and lastElement and select that.
	if(collapsed) {
		var text = document.createTextNode("");
		text = lastElement.parentNode.insertBefore(text, lastElement);
		range.selectNodeContents(text);
		range.collapse(true);
	}
	else {
		range = document.createRange();
		range.setStartAfter(firstElement);
		range.setEndBefore(lastElement);				
	}

	return range;

}




/** Mozile Style Command Object -
 * Wraps the selection in span tags, or uses the style attribute to add CSS to a given selection. Behaves in most ways like the MozileWrapCommand it inherits from. 
 * <p>Configuration String Requirements: "mode=[inline|toggle|toggleBlock|block], style=[CSS style rules]", for example "mode=toggle, style='color: red; font-weight: bold".
 * @constructor
 *
 * @param configArray An array of configuration information and options, created from a fongiuration string by the Mozile.parseConfig() method.
 * @return A string indicating success or an error.
 */
MozileStyleCommand.prototype = new MozileWrapCommand();
MozileStyleCommand.prototype.constructor = MozileStyleCommand;
MozileStyleCommand.superclass = MozileWrapCommand.prototype;

function MozileStyleCommand() {

	// If any arguments are passed to this command, then initialize it. Otherwise return.
	if(arguments.length > 0){
		var configArray = arguments[0];
		this.init(configArray);
	}
	else return true;

	/**** DEFINITIONS ****/
	// Define the properties for this object.

	/** Mozile Style Command - Tag -
	 * The element which will be used as a wrapper.
	 */
	this.tag = "span";

	/** Mozile Style Command - Attribute -
	 * The attribute which will be used instead of a wrapper element.
	 */
	this.attribute = "style";

	/** Mozile Style Command - Mode -
	 * Selects the way in which this command operates. Can be "inline", "toggle", or "block".
	 */
	this.mode = null;

	/** Mozile Style Command - Style -
	 * The CSS style rules which will be used.
	 */
	this.style = null;
	

	/**** VALIDATION ****/
	// Check to make sure that values are valid, then set them.
	
	// Check to see if a valid mode was provided.
	if(configArray['mode'] && typeof(configArray['mode'])!="null") {
		this.mode = configArray['mode'];
	}
	else {
		return "Error initializing MozileStyleCommand object -- invalid mode provided: "+configArray['mode'];
	}
	
	// Check to see if a valid mode was provided.
	if(configArray['style'] && typeof(configArray['style'])!="null") {
		this.style = configArray['style'];
	}
	else {
		return "Error initializing MozileStyleCommand object -- invalid style provided: "+configArray['style'];
	}

	return true;
}



/** Mozile Style Command - Is Active -
 * Checks to see if the current node has a parent which has this.attribute matching this.style.
 *
 * @return True of the selection has a parent which has this.attribute matching this.style, false otherwise.
 */
MozileStyleCommand.prototype.isActive = function() {
	var f = new Array();
	f["File"] = "XHTMLBasic/XHTMLBasic.js";
	f["Function"] = "MozileStyleCommand.isActive()";
	this.debug(f,0,"Checking to see if this command is active");

	var selection = window.getSelection();
	var range = selection.getRangeAt(0);
	var node = range.commonAncestorContainer;
	var flag = false;
	while(node) {
		// If the node is an element with this.attribute, and the value matches [beginning or non-word character]this.style, then the command is active.
		if(node.nodeType==1 && node.getAttribute(this.attribute) && node.getAttribute(this.attribute).match("(^|\W)"+this.style) ) {
			flag = true;
			break;
		} 
		node = node.parentNode;
	}

	return flag;
}



/** Mozile Style Command - Command -
 * Behaves according to the mode. For "inline" it wraps the current selection with this.tag styled with this.style. In the case that the selection spans multiple blocks, it wraps the whole range of the selection. "toggle" behaves like "inline", except that it will unwrap the current selection if it is already wrapped. The "block" mode adds this.style to the this.attribute of the parentBlock. "toggleBlock" behaves like "block" except that it will remove this.style from the parentBlock if it is already present.
 *
 * @param event The event which triggered this command.
 * @return Always true.
 */
MozileStyleCommand.prototype.command = function(event) {
	var f = new Array();
	f["File"] = "XHTMLBasic/XHTMLBasic.js";
	f["Function"] = "MozileStyleCommand.command()";
	this.debug(f,1,"Executing command "+event);
	
	var selection = window.getSelection();
	var range = selection.getRangeAt(0).cloneRange();
	
	var container, element, text, style, oldRange, blocks, firstElement, lastElement, i, flag;
	

	// Handle the wrap based on the mode
	switch (this.mode) {
		// Inline Case
		case "inline":
			// if the selection is collapsed, just add a new wrapper element with and empty text node
			if(selection.isCollapsed) {
				element = document.createElement(this.tag);
				element.setAttribute("style", this.style);
				text = document.createTextNode("");
				element.appendChild(text);
				range.insertNode(element);
				range.selectNode(text);
				range.collapse(true);
				selection.removeAllRanges();
				selection.addRange(range);
			}
			
			// if the selection is not collapsed, wrap the whole range
			else {
				element = document.createElement(this.tag);
				element.setAttribute("style", this.style);
				range = this.wrapRange(range, element)
				selection.removeAllRanges();
				selection.addRange(range);
			}
				
			return true;
			break;


		// Inline Toggle Case
		case "toggle":
			if(this.isActive()) {
				
				// get the element which makes this selection active
				container = range.commonAncestorContainer;
				while(container) {
					if(container.nodeType==1 && container.getAttribute("style") && container.getAttribute("style").match(this.style) ) {
						break;
					} 
					container = container.parentNode;
				}
				
				element = document.createElement(this.tag);
				element.setAttribute("style", this.style);
				range = this.unwrapRange(container, range, element);
				selection.removeAllRanges();
				selection.addRange(range);
			
			}
			else {
		
				// if the selection is collapsed, just add a new wrapper element with and empty text node
				if(selection.isCollapsed) {
					element = document.createElement(this.tag);
					element.setAttribute("style", this.style);
					text = document.createTextNode("");
					element.appendChild(text);
					range.insertNode(element);
					range.selectNode(text);
					range.collapse(true);
					selection.removeAllRanges();
					selection.addRange(range);
				}
				
				// if the selection is not collapsed, then things get trickier
				// The idea is to get all the blocks inside the selection. The first an last are wrapped inline, while the middle blocks are wrapped as whole blocks.
				else {
					element = document.createElement(this.tag);
					element.setAttribute("style", this.style);
					range = this.wrapRange(range, element);
					selection.removeAllRanges();
					selection.addRange(range);	
				}
			}
				
			return true;
			break;

		// Block toggle case
		case "toggleBlock":
			if(this.isActive()) {
				container = range.commonAncestorContainer;
				while(container) {
					if(container.nodeType==1 && container.getAttribute(this.attribute) && container.getAttribute(this.attribute).match(this.style) ) {
						break;
					} 
					container = container.parentNode;
				}
				element = container;
				style = element.getAttribute(this.attribute);
				style = style.replace(this.style,"");
				element.setAttribute(this.attribute, style);
			}
			else {
				container = range.commonAncestorContainer;
				element = container.parentBlock;
				style = element.getAttribute(this.attribute);
				if(style && style != "") style = style + " "+ this.style;
				else style = this.style;
				element.setAttribute(this.attribute, style);
			}
			
			return true;
			break;


		// All other cases (particularly "block")
		default:
			container = range.commonAncestorNode;
			element = container.parentBlock;
			style = element.getAttribute(this.attribute);
			if(style && style != "") style = style + " "+ this.style;
			else style = this.style;
			element.setAttribute(this.attribute, style);

			return true;	
			break;
	}
	
	return true;
	
}






/** Range Get TextNodes -
 * Returns an array of all the text nodes which are contained within the range.
 *
 * @return An array of text nodes.
 */
Range.prototype.getTextNodes = function() {
			
	// clean up the text nodes in commonAncestorContainer
	var container = this.commonAncestorContainer;
	container.normalize();
	
	var previousNode, startNode, finishNode;
	
	// If the start or end nodes aren't text this won't work
	if(this.startContainer.nodeType!=3) return false;
	if(this.endContainer.nodeType!=3) return false;
	
	// split text nodes at start and end, making sure the startNode is before the finishNode.
	finishNode = this.endContainer.splitText(this.endOffset);
	startNode = this.startContainer.splitText(this.startOffset);
	
	previousNode = startNode.previousSibling;
	
	//alert("Nodes "+startNode.textContent+"\n==\n"+finishNode.textContent);

	// Create the tree walker which will get all text nodes in commonAncestorContainer
	var treeWalker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null, false);
	
	// recurse until we find the startNode
	while(treeWalker.currentNode!=treeWalker.lastNode && treeWalker.currentNode != startNode) {
		treeWalker.nextNode();
	}
	
	// collect the nodes which we should wrap:
	// not white-space only, not already inside this.tag, not inside an "a" tag
	var textNodes = new Array();	
	
	var currentNode = treeWalker.currentNode;
	while(currentNode && currentNode != finishNode) {
		if(currentNode.parentNode &&
			currentNode.parentNode.nodeName.toLowerCase()!="a" &&
			matchNonWS.test(currentNode.textContent)  ) {
			textNodes.push(currentNode);
		}
		currentNode = treeWalker.nextNode();
	}

	return textNodes;

}



/** Range Get TextNodes -
 * Returns an array of all the block nodes which are contained within the range. A block can be:
 * - an element with Node.isBlock=true, but not "ul" or "ol"
 * - a text node which is inside the range but not inside any contained block
 *
 * @return An array of block nodes.
 */
Range.prototype.getBlocks = function() {

	var blockNodes = new Array();
	
	// If the range is contained in one node, return an empty array
	if(this.startContainer==this.endContainer || this.startContainer.parentNode == this.endContainer.parentNode) return blockNodes;

	var container = this.commonAncestorContainer;
	
	// Create the tree walker which will get all elements and text nodes in the container.
	var treeWalker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT, null, false);
	
	var currentNode = treeWalker.currentNode;
	var flag = false;
	var textFlag = false;
	var parentBlock;

	// Loop over the nodes.	
	while(currentNode) {
		
		// when we hit the startContainer, set the flag=true and add the parent block to the blockNodes array.
		if(currentNode == this.startContainer) {
			flag=true;
			// If this is a text node at the top level of the container, then it counts as it's own block.
			if(currentNode.nodeType==3 && this.startContainer.parentNode==container) {
				blockNodes.push(currentNode);
			}
			// Otherwise, add its parentBlock.
			else {
				blockNodes.push(currentNode.parentBlock);
			}
		}
		
		// If the flag is true, try to collect all the blocks.
		if(flag) {
		
			// If the currentNode is an element, not "ul" or "ol", is a block level node, and not an ancestor of the endContainer, then add it to the list. 
			if(currentNode.nodeType==1 && 
				currentNode.nodeName.toLowerCase() != "ul" && 
				currentNode.nodeName.toLowerCase() != "ol" && 
				currentNode.isBlock() && 
				!currentNode.isAncestorOf(this.endContainer) ) {
				
				blockNodes.push(currentNode);
			}


			// If the currentNode is a text node, not the start container, and non-empty, then check to see if its parentBlock is already included. If so, ignore it. If not, add currentNode as a block.
			if(currentNode.nodeType==3 && 
				currentNode!=this.startContainer &&  
				matchNonWS.test(currentNode.textContent) ) {
				
				textFlag = true;
				parentBlock = currentNode.parentBlock;
				for(var i=0; i<blockNodes.length; i++) {
					if(parentBlock == blockNodes[i])	{
						textFlag = false;
						break;
					}
				}
				// If the node was not caught by that check, add it to the block list.
				if(textFlag) { 
					blockNodes.push(currentNode);
				}
			}

		}
		if(currentNode == this.endContainer) break;
		currentNode = treeWalker.nextNode();
	}

	return blockNodes;

}










/** Mozile Insert Command - Object -
 * Creates a node according to a given function, and inserts it at the current selection.
 * <p>Configuration String Requirements: none. However, the object.createNode method must be set to return whatever object will be inserted (a string, nodes, etc.)
 * @constructor
 *
 * @param configArray An array of configuration information and options, created from a fongiuration string by the Mozile.parseConfig() method.
 * @return True if successful, an error message otherwise.
 */
MozileInsertCommand.prototype = new MozileCommand();
MozileInsertCommand.prototype.constructor = MozileInsertCommand;
MozileInsertCommand.superclass = MozileCommand.prototype;

function MozileInsertCommand() {

	// If any arguments are passed to this command, then initialize it. Otherwise return.
	if(arguments.length > 0){
		var configArray = arguments[0];
		this.init(configArray);
	}
	else return true;


	/**** DEFINITIONS ****/
	// Define the properties for this object.

	/** Mozile Insert Command - Insert -
	 * The object which will be inserted by this command. Could be a string, node, or document fragment. Must be the kind of object that Range.insertNode() will operate on.
	 */
	this.insert = null;

	return true;
}





/** Mozile Insert Command - Command -
 * Insert the object in this.insert into the document at the current selection. If the selection is not collapsed, then the contents will be deleted.
 * TODO: Restore the selection properly.
 *
 * @param event The event which triggered this command.
 * @return Always true.
 */
MozileInsertCommand.prototype.command = function(event) {
	var f = new Array();
	f["File"] = "XHTMLBasic/XHTMLBasic.js";
	f["Function"] = "MozileInsertCommand.command()";
	this.debug(f,1,"Executing command "+event);

	var node = this.createNode();
	if(node) {
		var range = window.getSelection().getRangeAt(0);
		range.deleteContents();
		range.insertNode(node);
		return true;
	}
	else {
		this.debug(f,2,"Nothing to insert!");
		return false;
	}

}





// Finally, register the module
mozile.registerModule("XHTMLBasic","0.7.0");





Documentation generated by JSDoc on Wed Oct 19 19:25:33 2005