tidy.js

Summary

Tools for serializing the DOM in a tidy way. Formatting follows the example of the HTML Tidy project and should match Tidy with indenting enabled and wrapping set to 0.

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

Author: James A. Overton


/* ***** 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 serializing the DOM in a tidy way. Formatting follows the example of the HTML Tidy project <http://tidy.sourceforge.net/> and should match Tidy with indenting enabled and wrapping set to 0.
 * @link http://mozile.mozdev.org 
 * @author James A. Overton <james@overton.ca>
 * @version 0.8
 * $Id: overview-summary-tidy.js.html,v 1.1 2008/02/20 18:47:09 jameso Exp $
 */

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

/**
 * Tools for formatting output.
 * @type Object
 */
mozile.save.tidy = new Object();
// JSDoc hack
mozile.save.tidy.prototype = new mozile.Module;


/** TODO:
 * - Add support for a "root" element.
 * - Should be XML general.
 * - General refactoring.
 */


/**
 * Determines whether spaces are added to the output.
 * @type Boolean
 */
mozile.save.tidy.spaces = true;

/**
 * Sets the newline character.
 * @type String
 */
mozile.save.tidy.newline = "\n";

/**
 * Sets the tab character.
 * @type String
 */
mozile.save.tidy.tab = "  ";

/**
 * Determines whether the given node is generic XML or not (i.e. HTML or XHTML).
 * @param {Node} node The node to start with.
 * @type Boolean
 */
mozile.save.tidy.isXML = function(node) {
	if(node.namespaceURI && node.namespaceURI != mozile.xml.ns.xhtml)
		return true;
	else return false;
}

/**
 * Counts the number of ancestor nodes this element has. Stops at the document node.
 * @param {Node} node The node to start with.
 * @type Integer
 */
mozile.save.tidy.countAncestors = function(node) {
	var i = -1;
	while(node && node != document) {
		i++;
		node = node.parentNode;
	}
	return i;
}


/**
 * Calculates the amount of space to add before the node.
 * @param {Node} node The node to calculate the spacing for.
 * @type String
 */
mozile.save.tidy.spaceBefore = function(node) {
	var output = "";
	if(!this.spaces) return output;

	switch(node.nodeName.toLowerCase()) {
		case "html": return "";
		case "head":
		case "body":
			return this.newline;
		case "textarea":
			output = this.newline;
			for(var i=1; i < this.countAncestors(node); i++) output += this.tab;
			break;
	}

	if(node.parentNode && node.parentNode.nodeName.toLowerCase() == "head") {
		return this.newline + this.tab;
	}

	if(mozile.edit.isBlock(node) || mozile.save.tidy.isXML(node)) {
		output = this.newline;
		for(var i=1; i < this.countAncestors(node); i++) output += this.tab;
	}

	return output;
}

/**
 * Calculates the amount of space to add between child nodes.
 * @param {Node} node The node to calculate the spacing for.
 * @type String
 */
mozile.save.tidy.spaceBetween = function(node) {
	var output = "";
	if(!this.spaces) return output;

	if(mozile.edit.isBlock(node) || mozile.save.tidy.isXML(node)) 
		return this.newline;

	switch(node.nodeName.toLowerCase()) {
		case "textarea":
			output = this.newline;
			break;
	}
	return output;
}

/**
 * Calculates the amount of space to add after the node.
 * @param {Node} node The node to calculate the spacing for.
 * @type String
 */
mozile.save.tidy.spaceAfter = function(node) {
	var output = "";
	if(!this.spaces) return output;

	switch(node.nodeName.toLowerCase()) {
		case "head":
		case "body":
		case "script":
		case "style":
		case "ul":
		case "ol":
		case "dl":
			output = this.spaceBefore(node);
			break;
		case "html":
			output = this.newline + output;
	}

	// If it's XML and it has children, indent the closing tag.
	if(mozile.save.tidy.isXML(node) && mozile.dom.getFirstChildElement(node))
		output = this.spaceBefore(node);
	
	return output;
}

/**
 * Formats a node and its children as a string.
 * @param {Node} node The node to calculate the spacing for.
 * @type String
 */
mozile.save.tidy.formatNode = function(node) {
	var output = "";
	var indent = "";
	switch(node.nodeType) {
		case mozile.dom.ELEMENT_NODE:
			var name = this.formatName(node.nodeName);
			if(node.nodeName.toLowerCase() == "script") return "";
			output = this.spaceBefore(node);
			output += "<"+ name;
			
			// Attributes
			var i;
			if(node.attributes.length) {
				var attr = new Array();
				//for(i=0; i < node.attributes.length; i++) {
				for(i=node.attributes.length-1; i>=0; i--) { // reverse order for FF1.5
					var a = node.attributes[i];
					if(!a.nodeValue) continue;
					if(!a.specified) continue; // ignore if the user didn't set it
					if(a.nodeName.toLowerCase() == "contenteditable" && 
						a.nodeValue == "inherit") continue;
					attr.push(this.formatNode(a));
				}
				if(attr.length) output += " "+ attr.join(" ");
			}
			
			// Child Nodes
			if(node.childNodes.length) {
				output += ">";
				var children = new Array();
				for(i=0; i < node.childNodes.length; i++) {
					children.push(this.formatNode(node.childNodes[i]));
				}
				output += children.join("");
				output += this.spaceAfter(node);
				output += "</"+ name +">";
			}
			else output += "/>";
			break;
			
		case mozile.dom.ATTRIBUTE_NODE:
			output = node.nodeName +'="'+ node.nodeValue +'"';
			break;
			
		case mozile.dom.TEXT_NODE:
			if(node.data.match(/\w/)) output = mozile.save.cleanMarkup(node.data);
			else {
				if(node != node.parentNode.firstChild &&
					node != node.parentNode.lastChild) {
					output = this.spaceBetween(node.nextSibling);
				}
			}
			break;
			
		case mozile.dom.CDATA_SECTION_NODE:
			if(node.parentNode && node.parentNode.nodeName.toLowerCase() == "style")
				output = "/*<![CDATA[*/"+ node.data +"/*]]>*/";
			else if(node.parentNode && node.parentNode.nodeName.toLowerCase() == "script")
				output = "//<![CDATA[\n"+ node.data +"\n//]]>";
			else output = "<![CDATA["+ node.data +"]]>";
			break;
			
		case mozile.dom.ENTITY_REFERENCE_NODE:
			output = "UNHANDLED";
			break;
			
		case mozile.dom.ENTITY_NODE:
			output = "UNHANDLED";
			break;
			
		case mozile.dom.PROCESSING_INSTRUCTION_NODE:
			output = "<?"+ node.target +" "+ node.data +"?>";
			break;
			
		case mozile.dom.COMMENT_NODE:
			output = "<!--"+ node.data +"-->";
			break;
			
		case mozile.dom.DOCUMENT_NODE:
			output = this.formatNode(node.documentElement);
			break;
			
		case mozile.dom.DOCUMENT_TYPE_NODE:
			output = mozile.xml.serialize(node);
			break;
			
		case mozile.dom.DOCUMENT_FRAGMENT_NODE:
			output = "";
			break;
			
		case mozile.dom.NOTATION_NODE:
			output = "UNHANDLED";
			break;
	
		default:
			output = "UNHANDLED";
	}
	return output;
}

/**
 * Formats the name of a node using the mozile.save.format settings.
 * @param {String} name The name to be formatted.
 * @type String
 */
mozile.save.tidy.formatName = function(name) {
	if(!mozile.save || !mozile.save.format) return name;
	if(typeof(mozile.save.format) != "string") return name;
	if(mozile.save.format.toLowerCase() == "lowercase") return name.toLowerCase();
	if(mozile.save.format.toLowerCase() == "uppercase") return name.toUpperCase();
	return name;
}





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