rng.js

Summary

This file defines objects for parsing and using RelaxNG schemas.

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

Author: James A. Overton


Class Summary
mozile.rng.Attribute  
mozile.rng.Choice  
mozile.rng.Data  
mozile.rng.Define  
mozile.rng.Div  
mozile.rng.Element  
mozile.rng.Empty  
mozile.rng.Grammar  
mozile.rng.Group  
mozile.rng.Include  
mozile.rng.Interleave  
mozile.rng.Node  
mozile.rng.OneOrMore  
mozile.rng.Optional  
mozile.rng.Param  
mozile.rng.Ref  
mozile.rng.Schema  
mozile.rng.Start  
mozile.rng.Text  
mozile.rng.Validation  
mozile.rng.Value  
mozile.rng.ZeroOrMore  

/* ***** 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 This file defines objects for parsing and using RelaxNG schemas.
 * @link http://mozile.mozdev.org 
 * @author James A. Overton <james@overton.ca>
 * @version 0.8
 * $Id: overview-summary-rng.js.html,v 1.15 2008/02/20 18:47:09 jameso Exp $
 */


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


/**
 * Tools for parsing RelaxNG schemas and validating XML documents.
 * @type Object
 */
mozile.rng = new Object();
// JSDoc hack
mozile.rng.prototype = new mozile.Module;

/**
 * Indicates verbose debugging messages.
 * @type Boolean
 */
mozile.rng.debug = false;

/**
 * A seed for creating unique element ids.
 * @type Integer
 */
mozile.rng.idSeed = 1;


/**** Common Methods ****/
/**
 * NOTE: These functions are intended to be assigned as methods to RNG objects.
 * The idea is to maximize reuse of code without an awkward hiearchy of classes.
 * They will not work on their own.
 */

/**
 * Returns the 'name' attribute of the element, stripped of whitespace.
 * @type String
 */
mozile.rng.getName = function() {
	if(this._name) return this._name;
	if(this._element.getAttribute("name")) {
		var match = this._element.getAttribute("name").match(/^\s*(.+?)\s*$/);
		if(match && match.length == 2) {
			this._name = match[1];
			return this._name;
		}
	}
	throw Error("This RNG '"+ this.getType() +"' element must have a 'name' attribute.");
}

/**
 * Gets the local part of the qualified name of the element.
 * @type String
 */
mozile.rng.getLocalName = function() {
	if(this._localName) return this._localName;
	var name = this.getName();
	if(name.indexOf(":")) this._localName = name.substring(name.indexOf(":") + 1, name.length);
	else this._localName = name;
	return this._localName;
}

/**
 * Gets the prefix part of the qualified name of the element.
 * @type String
 */
mozile.rng.getPrefix = function() {
	if(this._prefix) return this._prefix;
	var name = this.getName();
	if(name.indexOf(":")) this._prefix = name.substring(0, name.indexOf(":"));
	else this._prefix = null;
	return this._prefix;
}

/**
 * Checks to see if a RNG element type is in a given list of types.
 * @param types The list to check. Can be a string or an array of strings.
 * @param type The type to find in the list.
 * @type Boolean
 */
mozile.rng.checkType = function(types, type) {
	if(typeof(types) == "string" && types == type) return true;
	if(types.length) {
		for(var i=0; i < types.length; i++) {
			if(types[i] == type) return true;
		}
	}
	return false;
}

/**
 * Validates nodes in a sequence. 
 * As long as the validation isValid, each RNG child is checked.
 * If there is a validation.getCurrentElement(), then we mark that element as the last valid element and try to advance to the nextSiblingElement.
 * @private
 * @param {Node} node The first node to be validated.
 * @param {mozile.rng.Validation} validation The validation object for this validation operation.
 * @type mozile.rng.Validation
 */
mozile.rng.validateSequence = function(node, validation) {
	if(!node) throw Error("mozile.rng.validateSequence() requires a node.");
	if(!validation) throw Error("mozile.rng.validateSequence() requires an mozile.rng.Validation object.");

	var RNGChildren = this.getChildNodes();
	if(RNGChildren.length == 0) return validation;

	for(var r=0; r < RNGChildren.length; r++) {
		if(!validation.isValid) break;

		validation = RNGChildren[r].validate(node, validation);

		if(node.nodeType == mozile.dom.ELEMENT_NODE && validation.isEmpty)
			validation.logError(this, node, "The parent element should be empty, but it contains child elements.");
		
		if(validation.getCurrentElement()) {
			node = validation.getCurrentElement();
			if(mozile.dom.getNextSiblingElement(node)) node = mozile.dom.getNextSiblingElement(node);
		}
	}
	
	return validation;
}

/**
 * Validates the set of the RNG children as many times as possible. 
 * When the validation of the set fails, the last successful validation object is returned.
 * @param {Node} node The node to be validated.
 * @param {mozile.rng.Validation} validation The validation object for this validation operation.
 * @type mozile.rng.Validation
 */
mozile.rng.validateMany = function(node, validation) {
	if(!node) throw Error("mozile.rng.validateMany() requires a node.");
	if(!validation) throw Error("mozile.rng.validateMany() requires an mozile.rng.Validation object.");

	var result;
	validation.count = 0;
	while(true) {
		result = validation.branch();
		result = this._validateSequence(node, result);

		if(result.isValid) {
			validation.count++;
			validation.merge(result);

			if(result.getCurrentElement() && mozile.dom.getNextSiblingElement(result.getCurrentElement()) ) {
				node = mozile.dom.getNextSiblingElement(result.getCurrentElement());
				continue;
			}
		}


		if(mozile.rng.debug) validation.append(result, true);
		break;
	}
	
	return validation;
}

/**
 * Validates the set of the RNG children where the order of validation does not matter.
 * @param {Node} node The node to be validated.
 * @param {mozile.rng.Validation} validation The validation object for this validation operation.
 * @type mozile.rng.Validation
 */
mozile.rng.validateInterleave = function(node, validation) {
	if(!node) throw Error("mozile.rng.validateInterleave() requires a node.");
	if(!validation) throw Error("mozile.rng.validateInterleave() requires an mozile.rng.Validation object.");
	
	validation.logMessage(this, node, "Validating interleave...");
	// create a new list of RNG children
	var RNGChildren = new Array();
	for(var c=0; c < this.getChildNodes().length; c++) {
		RNGChildren.push(this.getChildNodes()[c]);
	}
	
	var type;
	var length = RNGChildren.length;
	// while true:
	for(var i=0; i < length; i++) {
		// for each node try each RNG child on the list:
		for(var r=0; r < RNGChildren.length; r++) {
			var result = validation.branch();
			result = RNGChildren[r].validate(node, result);

			// if one works
			if(result.isValid) {
				// zeroOrMore and optional are special cases: check (count && count > 0)
				type = RNGChildren[r].getType();
				if( (RNGChildren[r].getType() == "optional" || 
					RNGChildren[r].getType() == "zeroOrMore") && 
					!result.count ) 
						continue;

				validation.merge(result);
				if(result.getCurrentElement() && mozile.dom.getNextSiblingElement(result.getCurrentElement()) ) {
					node = mozile.dom.getNextSiblingElement(result.getCurrentElement());
				}

				// remove the working RNG child from the list
				RNGChildren.splice(r, 1);
				break;
			}
		}
	}

	// if there are children left other than optional and zeroOrMore, fail
	if(RNGChildren.length > 0) {
		for(r=0; r < RNGChildren.length; r++) {
			if(!validation.isValid) break;
			type = RNGChildren[r].getType();
			if(type != "optional" && type != "zeroOrMore")
				validation.logError(this, node, "There were non-optional RNG children which did not match any elements.");
		}
	}
	
	if(validation.isValid) validation.logMessage(this, node, "Interleave is valid.");

	return validation;
}

/**
 * Combines this mozile.rng.Define (or mozile.rng.Start) object with another mozile.rng.Define (or mozile.rng.Start) object.
 * <p>At least one of the elements must have a 'combine' attribute.
 * If both elements have a 'combine' attribute, then the values must match.
 * The value of the 'combine' attribute can be either 'choice' or 'interleave'.
 * <p>If this object has a single child of the combineType, then it is used as a container for the new objects.
 * Otherwise a new container of choiceType is created and all children moved into it.
 * Similarly, if the given object has a single child of combineType, all of that object's children are moved into the container. 
 * Otherwise all the object's children are moved into the container.
 * <a href="http://www.oasis-open.org/committees/relax-ng/spec.html#IDA0KZR">Specification</a>
 * @param {mozile.rng.Node} target The object to combine with this object.
 * @type mozile.rng.Node
 */
mozile.rng.combine = function(target) {
	var combineType;
	var combineA = this._element.getAttribute("combine");
	var combineB = target._element.getAttribute("combine");

	if(combineA && combineB) {
		if(combineA == combineB) combineType = combineA;
		else throw Error("In order to combine RNG elements, their 'combine' attributes muct match. '"+ combineA +"' != '"+ combineB +"'");
	}
	else {
		if(combineA) combineType = combineA;
		else if(combineB) combineType = combineB; 
		else throw Error("In order to combine RNG elements, at least one must have a 'combine' attribute.");
	}
	
	if(combineType != "choice" && combineType != "interleave")
	 throw Error("RNG 'define' or 'start' elements can only have 'combine' attribute values of 'choice' or 'interleave', not '"+ combineType +"'.");
	this._element.setAttribute("combine", combineType);



	var combineObject;
	if(this.getChildNodes().length == 1 && 
		this.getChildNode(0).getType() == combineType) {
			combineObject = this.getChildNode(0);
	}
	else {
		var validation = new mozile.rng.Validation();
		combineObject = this.getSchema().parseElement(mozile.dom.createElementNS(mozile.xml.ns.rng, combineType), validation, true);
		this.appendChild(combineObject);
		while(this.getChildNodes().length > 1) {
			combineObject.appendChild(this.removeChild(this.getChildNode(0)));
		}
	}

	var targetObject = target;
	if(target.getChildNodes().length == 1 && 
		target.getChildNode(0).getType() == combineType) {
			targetObject = target.getChildNode(0);
  }
	while(targetObject.getChildNodes().length) {
		combineObject.appendChild(targetObject.removeChild(targetObject.getChildNode(0)));
	}

	return this;
}


/**
 * Represents a RelaxNG schema.
 * <p>A schema has a single "root" element, and can include zero or more document objects representing external RNG files.
 * @constructor
 * @param target Optional. An element or path to a file, which will be parsed.
 */
mozile.rng.Schema = function(target) {
	/**
	 * An associative array of document objects for this schema, each of which represents an external RNG file. Keys are filepaths, values are documents.
	 * @private
	 * @type Object
	 */
	this._documents = new Object();

	/**
	 * An associative array of all RNG objects in this schema. Keyes are types and values are arrays of objects.
	 * @private
	 * @type Object
	 */
	this._types = new Object();

	/**
	 * A multi-dimenstional associative array of all named RNG objects in this schema. Keyes are types and values are associative arrays of names. The values of the latter are arrays of objects.
	 * @private
	 * @type Object
	 */
	this._names = new Object();

	/**
	 * The unique root where validation begins. Can be an mozile.rng.Element or mozile.rng.Start node.
	 * @private
	 * @type mozile.rng.Node
	 */
	this._root = null;
	
	if(target) this.parse(target);
}
mozile.rng.Schema.prototype.constructor = mozile.rng.Schema;

/**
 * Returns "[object mozile.rng.Schema]".
 * @type String
 */
mozile.rng.Schema.prototype.toString = function() { return "[object mozile.rng.Schema]" };

/**
 * Gets the element's localName.
 * @type String
 */
mozile.rng.Schema.prototype.getType = function() {
	return "schema";
}

/**
 * Parses a RNG into a hierarchy of RNG nodes.
 * This method can handle XMl strings, file paths, and elements as input.
 * @param target Optional. A XML string, path to a file, or element to parse.
 * @type mozile.rng.Validation
 */
mozile.rng.Schema.prototype.parse = function(target) {
	var element;
	var filepath;
	if(typeof(target) == "string") {
		if(target.indexOf("<") > - 1) {
			element = mozile.xml.parse(target).documentElement;
			filepath = location.toString();
		}
		else {
			var doc = this.load(target);
			if(!doc) throw Error("Could not load schema '"+ target +"'.");
			element = doc.documentElement;
			filepath = target;
		}
	} 
	else if(target.nodeType) {
		element = target;
		filepath = location.toString();
	}
	else throw Error("Unknown target given in mozile.rng.Schema.parse().");

	if(!this.filepath) this.filepath = filepath;
	//alert("Filepath: "+ this.filepath);
	
	var validation = new mozile.rng.Validation();
	validation.logMessage(this, null, "Starting to parse schema");
	var result = this.parseElement(element, validation);
	if(!result) {
		validation.logError(this, null, "An invalid result was returned from the parsing operation: "+result);
		return validation;
	}
	var root = result;
	if(root.getType() == "element" || root.getType() == "grammar") {
		this._root = root;
	}
	else {
		validation.logError(this, null, "Schema has no root element.");
		return validation;
	}
	
	
	// Parse includes.
	var includes = this.getNodes("include");
	if(!includes) return validation;
	
	for(var i=0; i < includes.length; i++) {
		validation = includes[i].include(validation);
	}	
	return validation;
}

/**
 * Parses an RNG element into an mozile.rng.Node. Acts recursively on the node's children.
 * @param {Element} element The RNG element to parse.
 * @param {mozile.rng.Validation} validation The validation object for this validation operation.
 * @param {Boolean} omitValidation Optional. When true the element will not be self-validated.
 * @type mozile.rng.Node
 */
mozile.rng.Schema.prototype.parseElement = function(element, validation, omitValidation) {
	var rngNode = this._createRNGNode(element, validation);
	if(!rngNode) {
		//validation.logMessage(null, element, "Element '"+ element.nodeName  +"' is not an RNG element.");
		return null;
	}
	else validation.logMessage(rngNode, element, "Adding RNG '"+ rngNode.getType()  +"' node.");

	if(omitValidation != true) {
		validation = rngNode.selfValidate(validation);
		if(!validation.isValid) return {node: null, validation: validation};
	}

	if(element.hasChildNodes()) {
		var child;
		for(var c=0; c < element.childNodes.length; c++) {
			child = this.parseElement(element.childNodes[c], validation);
			if(child) rngNode.appendChild(child);
		}
	}
	
	return rngNode;
}

/**
 * Creates the right kind of RNG object for a given RNG element.
 * Adds the object to the list of descendants.
 * @private
 * @param {Element} element The RNG element to parse.
 * @type mozile.rng.Node
 */
mozile.rng.Schema.prototype._createRNGNode = function(element, validation) {
	if(!element.nodeType || element.nodeType != mozile.dom.ELEMENT_NODE) return null;
	if(element.namespaceURI != mozile.xml.ns.rng) return null;
	
	var result;
	var type = mozile.dom.getLocalName(element);
	switch(type) {
		case "grammar":     result = new mozile.rng.Grammar(element, this); break;
		case "start":       result = new mozile.rng.Start(element, this); break;
		case "element":     result = new mozile.rng.Element(element, this); break;
		case "attribute":   result = new mozile.rng.Attribute(element, this); break;
		case "empty":       result = new mozile.rng.Empty(element, this); break;
		case "text":        result = new mozile.rng.Text(element, this); break;
		case "group":       result = new mozile.rng.Group(element, this); break;
		case "optional":    result = new mozile.rng.Optional(element, this); break;
		case "oneOrMore":   result = new mozile.rng.OneOrMore(element, this); break;
		case "zeroOrMore":  result = new mozile.rng.ZeroOrMore(element, this); break;
		case "choice":      result = new mozile.rng.Choice(element, this); break;
		case "define":      result = new mozile.rng.Define(element, this); break;
		case "ref":         result = new mozile.rng.Ref(element, this); break;
		case "include":     result = new mozile.rng.Include(element, this); break;
		case "data":        result = new mozile.rng.Data(element, this); break;
		case "param":       result = new mozile.rng.Param(element, this); break;
		case "value":       result = new mozile.rng.Value(element, this); break;
		case "interleave":  result = new mozile.rng.Interleave(element, this); break;
		case "div":         result = new mozile.rng.Div(element, this); break;
		//case "mixed":       result = new mozile.rng.Mixed(element, this); break;
		default: 
			validation.logError(this, null, "Method mozile.rng.Schema._createRNGNode() found an unknown element '"+ element.nodeName +"' with the RNG namespace."); 
			return null;
	}

	if(result) {
		this._indexNode(result);
		return result;
	}
	else return null;

}

/**
 * Stores the RNG node in the appropriate spot inside the _types and _names objects.
 * @private
 * @param {mozile.rng.Node} node The node to index
 * @type Void
 */
mozile.rng.Schema.prototype._indexNode = function(node) {
	// Index by type
	var type = node.getType();
	if(!this._types[type]) this._types[type] = new Array();
	this._types[type].push(node);
	
	// Index by name
	if(node.getName) {
		var name = node.getName();
		if(!this._names[type]) this._names[type] = new Object();
		if(!this._names[type][name]) this._names[type][name] = new Array();
		this._names[type][name].push(node);
	}
}

/**
 * Returns an array of nodes of the given type.
 * @param {String} type A node type, such as "element" or "zeroOrMore".
 * @type Array
 */
mozile.rng.Schema.prototype.getNodes = function(type, name) {
	if(name) {
		if(this._names[type] && this._names[type][name]) 
			return this._names[type][name];
		else return new Array();
	}
	else {
		if(this._types[type]) return this._types[type];
		else return new Array();
	}
}


/**
 * Loads an external RNG XML file, adds it to the documents array, and returns it. 
 * If the file has already been loaded a stored copy will be used unless forceLoad is true. 
 * @param {String} filepath The path to the RNG XML file.
 * @param {Boolean} forceLoad Optional. When true the method will not check for a cached copy of the document.
 * @type Document
 */
mozile.rng.Schema.prototype.load = function(filepath, forceLoad) {
	if(!forceLoad && this._documents[filepath]) return this._documents[filepath];

	var uri = mozile.getPath(filepath);
	uri = mozile.getAbsolutePath(uri, mozile.root);

	var xmlDoc = mozile.xml.load(uri);
	if(xmlDoc) this._documents[filepath] = xmlDoc;

	return xmlDoc;
}

/**
 * Validates an element against the schema.
 * Before validating, all caches are reset.
 * @param {Element} element Optional. The element to validate. If not is given, th e documentElement is used.
 * @type Boolean
 */
mozile.rng.Schema.prototype.validate = function(element) {
	if(!element) element = document.documentElement;
	if(!this._root) return false;

	var validation = new mozile.rng.Validation();
	this._root.resetAll(); // clear all cached values
	this._root.validate(element, validation);

	return validation;
}



/**
 * Represents the result of validating an element.
 * <p>This object is used to pass inforamtion down the RNG element hierarchy to an element which is validating its children. It stores information about the validity of the child, as well as: the last validated node, the attributes which have been validated, and whether text is allowed in this element.
 * @constructor
 */
mozile.rng.Validation = function() {
	/*
	 * A Boolean which indicates whether the validation succeeded or failed.
	 * @type Boolean
	 */
	this.isValid = true;

	/*
	 * A Boolean which indicates whether the element should be empty.
	 * @type Boolean
	 */
	this.isEmpty = false;

	/*
	 * A Boolean which indicates whether non-whitespace text nodes are allowed as children of the element.
	 * @type Boolean
	 */
	this.allowText = false;


	/*
	 * The element currently being validated.
	 * @private
	 * @type Boolean
	 */
	this._currentElement = null;

	/*
	 * The oparent of the element currently being validated.
	 * @private
	 * @type Boolean
	 */
	this._currentParent = null;

	/*
	 * The last element to be successfully validated.
	 * @private
	 * @type Boolean
	 */
	this._lastValidElement = null;

	/*
	 * An array containing the names of validated attributes.
	 * @private
	 * @type Array
	 */
	this._validAttributes = new Array();

	/*
	 * An array containing all the messages that have been logged.
	 * @private
	 * @type Array
	 */
	this._messages = new Array();

}

/**
 * Returns "[object mozile.rng.Validation]".
 * @type String
 */
mozile.rng.Validation.prototype.toString = function() { return "[object mozile.rng.Validation]" };

/**
 * Gets the object's type.
 * @type String
 */
mozile.rng.Validation.prototype.getType = function() {
	return "validation";
}

/**
 * Gets the last valid element.
 * @type Element
 */
mozile.rng.Validation.prototype.getCurrentElement = function() { 
	return this._currentElement;
}

/**
 * Sets the element to be validated and marks the last element as validated.
 * @type Element
 */
mozile.rng.Validation.prototype.setCurrentElement = function(element) {
	if(element.nodeType == mozile.dom.ELEMENT_NODE) {
		if(element == this._lastValidElement) {
			this.logError(this, element, "An element has been marked as valid twice, which indicated an error");
		}
		else {
			this._lastValidElement = this._currentElement;
			this._currentElement = element;
		}
	}
	return element;
}

/**
 * Gets the current parent element.
 * @type Element
 */
mozile.rng.Validation.prototype.getCurrentParent = function() { 
	return this._currentParent;
}

/**
 * Sets the current parent element.
 * @type Element
 */
mozile.rng.Validation.prototype.setCurrentParent = function(element) {
	this._currentParent = element;
	return element;
}


/**
 * Adds an attribute to the list of valid attributes.
 * @param {Attr} attr Th attribute node to be added.
 * @type Attr
 */
mozile.rng.Validation.prototype.addAttribute = function(attr) { 
	this._validAttributes.push(attr);
	return attr;
}

/**
 * Determines whether an attribute is on the validAttributes list.
 * @param {Attr} attr The attribute to check.
 * @type Boolean
 */
mozile.rng.Validation.prototype.isAttributeValid = function(attr) {
	for(var a=0; a < this._validAttributes.length; a++) {
		//alert(this._validAttributes[a].nodeName +" = "+ attr.nodeName +" ? "+ (this._validAttributes[a] == attr));
		// Old versions of Mozilla mess up attribute identity checks.
		if(mozile.browser.isMozilla && mozile.browser.mozillaVersion < 1.8) {
			if(this._validAttributes[a].nodeName == attr.nodeName) return true;
		}
		if(this._validAttributes[a] == attr) return true;
	}
	return false;
}

/**
 * Logs a message object to the messae array.
 * @param {mozile.rng.Node} RNG node The node which is logging the message.
 * @param {Node} node The current node being validated.
 * @param {String} message The message to log.
 * @param {String} type Optional. The message type.
 * @type Void
 */
mozile.rng.Validation.prototype.logMessage = function(rngNode, node, message, type) {
	if(!type) type = "Message";
	this._messages.push({"type": type, "rngNode": rngNode, "node": node, "message": message});
	//if(errorsCheck.checked) {
	//	var name = rngNode.getType();
	//	if(rngNode.getName) name += ":"+ rngNode.getName();
	//	alert(name +" "+ message);
	//}
}

/**
 * Logs a parsing error message object to the messae array, and sets isValid to false.
 * A parsing error indicates that the XML failed validation.
 * @param {mozile.rng.Node} RNG node The node which is logging the message.
 * @param {Node} node The current node being validated.
 * @param {String} message The message to log.
 * @type Void
 */
mozile.rng.Validation.prototype.logError = function(rngNode, node, message) { 
	this.logMessage(rngNode, node, message, "Error");
	this.isValid = false;
}

/**
 * Returns the first error message.
 * @type Object
 */
mozile.rng.Validation.prototype.getFirstError = function() {
	for(var m=0; m < this._messages.length; m++) {
		if(this._messages[m].type != "Message") return this._messages[m];
	}
	return null;
}

/**
 * Creates a new Validation object, and copies some of the properties of the current object to the new object.
 * @type mozile.rng.Validation
 */
mozile.rng.Validation.prototype.branch = function() {
	var validation = new mozile.rng.Validation();
	validation._currentParent = this._currentParent;
	return validation;
}

/**
 * Appends the messages from the given mozile.rng.Validation to this object's message list. If the given object is not valid, this object is set to isValid = false.
 * @param {mozile.rng.Validation} validation The node which is logging the message.
 * @param {Boolean} status Optional. THis parameter overrides the object's "isValid" property.
 * @type Void
 */
mozile.rng.Validation.prototype.append = function(validation, status) {
	this._messages = this._messages.concat(validation._messages);
	if(status) this.isValid = status;
	else if(!validation.isValid) this.isValid = false;
}

/**
 * Appends messages and then merges validAttributes lists from the given mozile.rng.Validation object with this object. 
 * If the given object allows text, this allowText is set to true. 
 * The currentElement and lastValidElement are set to match the given object.
 * @param {mozile.rng.Validation} validation The node which is logging the message.
 * @type Void
 */
mozile.rng.Validation.prototype.merge = function(validation) {
	this.append(validation);
	this._validAttributes = this._validAttributes.concat(validation._validAttributes);
	if(validation.isEmpty) this.isEmpty = true;
	if(validation.allowText) this.allowText = true;
	if(validation._currentElement) this._currentElement = validation._currentElement;
	if(validation._lastValidElement) this._lastValidElement = validation._lastValidElement;
}

/**
 * Prints a report of the validation operation.
 * @param {Boolean} errorsOnly Optional. When true, only errors will be reported.
 * @type String
 */
mozile.rng.Validation.prototype.report = function(errorsOnly) {
	mozile.require("mozile.util");
	mozile.require("mozile.xpath");

	var messages = new Array();
	var maxType = 8;
	var maxName = 0;
	var maxLocation = 0;
	var msg;
	var location;
	var rngName
	var lastNode;
	var lastLocation;
	for(var m=0; m < this._messages.length; m++) {
		msg = this._messages[m];
		if(errorsOnly && msg.type == "Message") continue;


		rngName = "validation";
		if(msg.rngNode) {
			if(msg.rngNode.getType()) rngName = mozile.util.pad(msg.rngNode.getType(), 11, true);
			//var rngName = msg.rngNode._element.getXPath();
			if(msg.rngNode.getName) rngName = rngName +" "+ msg.rngNode.getName();
		}

		
		location = "";
		if(msg.node) {
			if(msg.node == lastNode) location = lastLocation;
			else {
				location = mozile.xpath.getXPath(msg.node);
				location = location.replace(/xmlns:/g, "");
				//var depth = 0;
				//var parent = msg.node.parentNode;
				//while(parent) {
				//	depth++;
				//	parent = parent.parentNode;
				//}
				//var space = "";
				//for(var d=0; d < depth; d++) space += " ";
				//location = space + msg.node.nodeName;
				lastNode = msg.node;
				lastLocation = location;
				if(location.length > maxLocation) maxLocation = location.length;
			}
		}
		
		//if(msg.type.length > maxType) maxType = msg.type.length;
		if(rngName.length > maxName) maxName = rngName.length;
		
		messages.push([msg.type, rngName, location, msg.message]);
	}

	var output = new Array();
	//if(this.isValid) output.push("VALIDATION SUCCESSFUL");
	//else output.push("VALIDATION FAILED");

	var maxNum = Math.ceil( Math.log(messages.length+1) / Math.log(10) ) + 1;
	for(m=0; m < messages.length; m++) {
		msg = messages[m];
		output.push( mozile.util.pad((m+1) +".", maxNum) +" "+ mozile.util.pad(msg[0], maxType) +" "+ mozile.util.pad(msg[1], maxName) +"  "+ mozile.util.pad(msg[2], maxLocation) +"  "+ msg[3] ); 
	}

	return output.join(mozile.linesep);
}


/**
 * Represents any element from a RelaxNG grammar.
 * @constructor
 * @param {Element} element The RNG element to represent.
 */
mozile.rng.Node = function(element, schema) {
	/**
	 * The element associated with this RNG node.
	 * @private
	 * @type Element
	 */
	this._element = element;

	/**
	 * The schema to which this RNG node belongs.
	 * @private
	 * @type mozile.rng.Schema
	 */
	this._schema = schema;
}

/**
 * An array of all cached properties. They can be cleared with the reset() method.
 * @private
 * @type Array
 */
mozile.rng.Node.prototype._cached = [this._mustHaveCache, this._mayHaveCache, this._descendantElementsCache, this._type];

/**
 * Returns the identifier "[object mozile.rng.Node]".
 * Works for any RNG node by capitalizing the type.
 * @type String
 */
mozile.rng.Node.prototype.toString = function() {
	var type = this.getType();
	type = type.charAt(0).toUpperCase() + type.substring(1);
	var name = "";
	if(this.getName) name = " '"+ this.getName() +"'";
	return "[object mozile.rng."+ type + name +"]";
}

/**
 * Gets the element's localName.
 * @type String
 */
mozile.rng.Node.prototype.getType = function() {
	if(!this._type) this._type = mozile.dom.getLocalName(this._element);
	return this._type;
}

/**
 * Gets the mozile.rng.Node's owner mozile.rng.Schema.
 * @type mozile.rng.Schema
 */
mozile.rng.Node.prototype.getSchema = function() {
	return this._schema;
}

/**
 * Gets the mozile.rng.Node's parent mozile.rng.Grammar.
 * @type mozile.rng.Grammar
 */
mozile.rng.Node.prototype.getGrammar = function() {
	var node = this;
	while(node) {
		if(node.getType() == "grammar") return node;
		node = node.getParentNode();
	}
	return null;
}

/**
 * Gets the element's parentNode.
 * @type mozile.rng.Node
 */
mozile.rng.Node.prototype.getParentNode = function() {
	if(this._parentNode) return this._parentNode;
	return null;
}

/**
 * Gets the element's first ancestor which is an mozile.rng.Element.
 * @type mozile.rng.Element
 */
mozile.rng.Node.prototype.getParentElement = function() {
	var parent = this.getParentNode();
	while(parent) {
		if(parent.getType() == "element") return parent;
		parent = parent.getParentNode();
	}
	return null;
}

/**
 * Gets the element's parentNode.
 * @type String
 */
mozile.rng.Node.prototype.getNextSibling = function() {
	var parent = this.getParentNode();
	if(!parent) return null;
	var siblings = parent.getChildNodes();
	for(var r=0; r < siblings.length; r++) {
		if(siblings[r] == this) {
			if(siblings[r+1]) return siblings[r+1];
		}
	}
	return null;
}

/**
 * Gets an array of the node's child nodes.
 * @type Array
 */
mozile.rng.Node.prototype.getChildNodes = function() {
	if(!this._childNodes) this._childNodes = new Array();
	return this._childNodes;
}

/**
 * Gets the child nodes at the given index.
 * @param {Integer} index
 * @type mozile.rng.Node
 */
mozile.rng.Node.prototype.getChildNode = function(index) {
	var children = this.getChildNodes();
	if(children && children.length && 
		children.length > 0 && index < children.length) 
			return children[index];
	else return null;
}

/**
 * Appends the child to this object's list of child nodes and sets its parentNode.
 * @param {mozile.rng.Node} child The child to be appended.
 * @type mozile.rng.Node
 */
mozile.rng.Node.prototype.appendChild = function(child) {
	if(child) {
		this.getChildNodes().push(child);
		child._parentNode = this;
	}
	return child;
}

/**
 * Removes the child from this object's list of child node, and unsets its parentNode.
 * @param {mozile.rng.Node} child The child to be removed.
 * @type mozile.rng.Node
 */
mozile.rng.Node.prototype.removeChild = function(child) {
	var children = new Array();
	for(var c=0; c < this.getChildNodes().length; c++) {
		if(this.getChildNodes()[c] == child) continue;
		children.push(this.getChildNodes()[c]);
	}
	if(children.length == this.getChildNodes().length)
		throw Error("mozile.rng.Node.removeChild(): The given node is not child of this node.");
	else this._childNodes = children;
	child._parentNode = null;
	return child;
}

/**
 * Gets an array of all the elements which descend from this node.
 * @param types The RNG element type(s) of search for. Can be a string or an array of strings. E.g. "element".
 * @param {Boolean} deep Optional. If the current node is an element and "deep" is true, then its children are included.
 * @type Array
 */
mozile.rng.Node.prototype.getDescendants = function(types, deep) {
	//if(this._descendantElementsCache) return this._descendantElementsCache;
	if(deep !== false) deep = true;
	
	var descendants = new Array();
	if(!deep && mozile.rng.checkType(types, this.getType())) {
		descendants.push(this);
	}
	else {
		var result, skip, child;
		for(var c=0; c < this.getChildNodes().length; c++) {
			child = this.getChildNode(c);

			// If the child may not have the right type, then skip it.
			skip = true;
			if(typeof(types) == "string" && child.mayHave(types)) skip = false;
			else if(types.length) {
				for(i=0; i < types.length; i++) {
					if(!child.mayHave(types[i])) continue;
					skip = false;
					break;
				}
			}
			if(skip) continue;

			result = child.getDescendants(types, false);
			descendants = descendants.concat(result);
		}
	}
	//alert(this.getType() +" "+ this._name +" "+ deep +"\n"+ descendants);
	
	//this._descendantElementsCache = descendants;
	return descendants;
}

/**
 * True if the current element must contain at least one child of the given type.
 * @param {String|Element} type Either the type of node or a node.
 * @param {String} name Optional. The name of the node.
 * @type String
 */
mozile.rng.Node.prototype.generateKey = function(type, name) {
	var key = "unknown";
	if(typeof(type) == "string") key = type;
	else if(typeof(type) == "object" && type._element) {
		if(!type._element.getAttribute("id")) 
			type._element.setAttribute("id", "mozileRNG-"+ mozile.rng.idSeed++);
		key = type._element.getAttribute("id");
	}
	if(name) key += "_"+ String(name);
	return key;
}

/**
 * True if the current element must contain at least one child of the given type.
 * @param {String|Element} type Either the type of node to be matched or the exact node to be matched.
 * @param {String} name Optional. The name to be matched.
 * @type Boolean
 */
mozile.rng.Node.prototype.mustContain = function(type, name) {
	var key = this.generateKey(type, name);
	if(!this._mustHaveCache) this._mustHaveCache = new Object();
	if(this._mustHaveCache[key]) return this._mustHaveCache[key];

	var result = false;
	for(var c=0; c < this.getChildNodes().length; c++) {
		if(this.getChildNodes()[c].mustHave(type, name)) {
			result = true;
			break;
		}
	}

	this._mustHaveCache[key] = result;
	return result;
}

/**
 * True if the current element may contain at least one child of the given type.
 * @param {String|Element} type Either the type of node to be matched or the exact node to be matched.
 * @param {String} name Optional. The name to be matched.
 * @type Boolean
 */
mozile.rng.Node.prototype.mayContain = function(type, name) {
	var key = this.generateKey(type, name);
	if(!this._mayHaveCache) this._mayHaveCache = new Object();
	if(this._mayHaveCache[key]) return this._mayHaveCache[key];

	var result = false;
	for(var c=0; c < this.getChildNodes().length; c++) {
		if(this.getChildNodes()[c].mayHave(type, name)) {
			result = true;
			break;
		}
	}

	this._mayHaveCache[key] = result;
	return result;
}

/**
 * True if the current element is of the given type or must contain at least one child of the given type.
 * @param {String|Element} type Either the type of node to be matched or the exact node to be matched.
 * @param {String} name Optional. The name to be matched.
 * @type Boolean
 */
mozile.rng.Node.prototype.mustHave = function(type, name) {
	if(this === type) return true;
	else if(this.getType() == type) {
		if(name && this.getName && name != this.getName()) return false;
		return true;
	}
	else return this.mustContain(type, name);
}

/**
 * True if the current element is of the given type or may contain at least one child of the given type.
 * @param {String|Element} type Either the type of node to be matched or the exact node to be matched.
 * @param {String} name Optional. The name to be matched.
 * @type Boolean
 */
mozile.rng.Node.prototype.mayHave = function(type, name) {
	if(this === type) return true;
	else if(this.getType() == type) {
		if(name && this.getName && name != this.getName()) return false;
		return true;
	}
	else return this.mayContain(type, name);
}

/**
 * Resets all cached data in this node.
 * @type Void
 */
mozile.rng.Node.prototype.reset = function() {
	for(var i=0; i < this._cached.length; i++) {
		this._cached[i] = undefined;
	}
}

/**
 * Resets all cached data for this node and all children.
 * @type Void
 */
mozile.rng.Node.prototype.resetAll = function() {
	this.reset();
	for(var c=0; c < this.getChildNodes().length; c++) {
		this.getChildNodes()[c].reset;
	}
}

/**
 * Validates this RNG object.
 * @type mozile.rng.Validation
 */
mozile.rng.Node.prototype.selfValidate = function(validation) {
	if(!mozile.dom.getFirstChildElement(this._element))
		validation.logError(this, this._element, "This RNG element must have at least one child element.");
	return validation;
}

/**
 * Validates this RNG object and its children.
 * @type mozile.rng.Validation
 */
mozile.rng.Node.prototype.selfValidateAll = function(validation) {
	validation = this.selfValidate(validation);
	validation.logMessage(this, null, "Self validated.");

	for(var r=0; r < this.getChildNodes().length; r++) {
		if(!validation.isValid) break;
		validation = this.getChildNodes()[r].selfValidateAll(validation);
	}
		
	return validation;
}

/**
 * Validates the node. This is an abstract method, meant to be overriden by child classes.
 * @param {Node} node The node to be validated.
 * @param {mozile.rng.Validation} validation The validation object for this validation operation.
 * @type mozile.rng.Validation
 */
mozile.rng.Node.prototype.validate = function(node, validation) {
	validation.logError(this, node, "The validate() method is not defined yet for "+ this +".");
	return validation;
}


/**
 * Represents an RNG "grammar" element.
 * @constructor
 * @param {Element} element The RNG element to represent.
 */
mozile.rng.Grammar = function(element, schema) {
	/**
	 * The element associated with this RNG node.
	 * @private
	 * @type Element
	 */
	this._element = element;

	/**
	 * The schema to which this RNG node belongs.
	 * @private
	 * @type mozile.rng.Schema
	 */
	this._schema = schema;

	/**
	 * A grammar can have a single start element.
	 * @private
	 * @type mozile.rng.Start
	 */
	this._start = null;

	/**
	 * A grammar can zero or more definitions. They are stored as an associative array with names as keys and mozile.rng.Define objects as values.
	 * @private
	 * @type Object
	 */
	this._definitions = new Object;
}
mozile.rng.Grammar.prototype = new mozile.rng.Node;
mozile.rng.Grammar.prototype.constructor = mozile.rng.Grammar;


/**
 * Gets the grammar's first ancestor which is an mozile.rng.Grammar.
 * @type mozile.rng.Grammar
 */
mozile.rng.Grammar.prototype.getParentGrammar = function() {
	var parent = this.getParentNode();
	while(parent) {
		if(parent.getType() == "grammar") return parent;
		parent = parent.getParentNode();
	}
	return null;
}

/**
 * Appends the given child to this object's list of children.
 * Also watches for a mozile.rng.Start object for this grammar.
 * @param {mozile.rng.Node} child The child to be appended.
 * @type mozile.rng.Node
 */
mozile.rng.Grammar.prototype.appendChild = function(child) {
	if(child && child.getType) {
		switch(child.getType()) {
			case "start":
				return this.setStart(child);
				break;
			case "define":
				return this.addDefinition(child);
				break;
			case "include":
			case "div":
				break;
			default:
				throw Error("RNG grammar element cannot have a child of type '"+ child.getType() +"'.");
				break;
		}
		this.getChildNodes().push(child);
		child._parentNode = this;
		
		//if(child.getType() == "include") child.include();
	}
	return child;
}

/**
 * Gets the start element. A grammar may have a single start element.
 * @type mozile.rng.Start
 */
mozile.rng.Grammar.prototype.getStart = function() {
	if(this._start) return this._start;
	else return null;
}

/**
 * Sets the start element. A grammar may have a single start element. If there is a previous start element, it is combined.
 * @type mozile.rng.Start
 */
mozile.rng.Grammar.prototype.setStart = function(start) {
	if(start.getType && start.getType() == "start") {
		if(this._start) this._start.combine(start);
		else {
			this._start = start;
			this.getChildNodes().push(start);
			start._parentNode = this;
		}
		return start;
	}
	else return null;
}

/**
 * Given a name, returns a definition object.
 * @param {String} name The name of the definition.
 * @type mozile.rng.Define
 */
mozile.rng.Grammar.prototype.getDefinition = function(name) {
	if(this._definitions[name]) return this._definitions[name];
	else return null;
}

/**
 * Adds the definition to the associative array of definitions.
 * @param {mozile.rng.Define} definition The definition to be added.
 * @type mozile.rng.Define
 */
mozile.rng.Grammar.prototype.addDefinition = function(definition) {
	if(this._definitions[definition.getName()]) {
		definition = this._definitions[definition.getName()].combine(definition);
	}
	else {
		this._definitions[definition.getName()] = definition;
		this.getChildNodes().push(definition);
		definition._parentNode = this;
	}
	return definition;
}

/**
 * True if the type is "grammar". Otherwise calls the mozile.rng.Start object's mustHave() method.
 * @param {String|Element} type Either the type of node to be matched or the exact node to be matched.
 * @param {String} name Optional. The name to be matched.
 * @type Boolean
 */
mozile.rng.Grammar.prototype.mustHave = function(type, name) {
	if(this === type) return true;
	else if(this.getType() == type) return true;

	var result = false;
	var start = this.getStart();
	if(start) result = start.mustHave(type, name);
	else throw Error("mozile.rng.Grammar.mustHave() requires getStart() to return a mozile.rng.Start object.");

	return result;
}

/**
 * True if the type is "grammar". Otherwise calls the mozile.rng.Start object's mayHave() method.
 * @param {String|Element} type Either the type of node to be matched or the exact node to be matched.
 * @param {String} name Optional. The name to be matched.
 * @type Boolean
 */
mozile.rng.Grammar.prototype.mayHave = function(type, name) {
	if(this === type) return true;
	else if(this.getType() == type) return true;

	var result = false;
	var start = this.getStart();
	if(start) result = start.mayHave(type, name);
	else throw Error("mozile.rng.Grammar.mayHave() requires getStart() to return a mozile.rng.Start object.");

	return result;
}

/**
 * Validates the node by calling the grammar's start node's validation method.
 * @param {Node} node The node to be validated.
 * @param {mozile.rng.Validation} validation Optional. The validation object for this validation operation.
 * @type mozile.rng.Validation
 */
mozile.rng.Grammar.prototype.validate = function(node, validation) {
	var start = this.getStart();
	if(start) validation = start.validate(node, validation);
	else validation.logError(this, node, "RNG grammar element must have a start element in order to validate a node, but it does not.");
	return validation;
}


/**
 * Represents an RNG "start" element.
 * @constructor
 * @param {Element} element The RNG element to represent.
 */
mozile.rng.Start = function(element, schema) {
	/**
	 * The element associated with this RNG node.
	 * @private
	 * @type Element
	 */
	this._element = element;

	/**
	 * The schema to which this RNG node belongs.
	 * @private
	 * @type mozile.rng.Schema
	 */
	this._schema = schema;
}
mozile.rng.Start.prototype = new mozile.rng.Node;
mozile.rng.Start.prototype.constructor = mozile.rng.Start;

/**
 * Combines this mozile.rng.Start object with another mozile.rng.Start object.
 * @type mozile.rng.Start
 */
mozile.rng.Start.prototype.combine = mozile.rng.combine;

/**
 * Validates this RNG object. Start elements must bust be children of a grammar and have child elements. If they have a "combine" attribute, the value must be "choice" or "interleave".
 * @type mozile.rng.Validation
 */
mozile.rng.Start.prototype.selfValidate = function(validation) {
	if(!this._element.parentNode || mozile.dom.getLocalName(this._element.parentNode) != "grammar")
		validation.logError(this, this._element, "This RNG element must be the child of a grammar element.");

	if(!mozile.dom.getFirstChildElement(this._element))
		validation.logError(this, this._element, "This RNG element must have at least one child element.");
	
	var combine = this._element.getAttribute("combine");
	if(combine) {
		if(combine != "choice" && combine != "interleave")
			validation.logError(this, this._element, "This RNG 'start' element has an invalid 'combine' attribute value of '"+ combine +"'.");
	}

	return validation;
}


/**
 * Validates the node by calling the validation method of the first RNG child object.
 * @param {Node} node The node to be validated.
 * @param {mozile.rng.Validation} validation Optional. The validation object for this validation operation.
 * @type mozile.rng.Validation
 */
mozile.rng.Start.prototype.validate = function(node, validation) {
	var RNGChild = this.getChildNode(0);
	if(RNGChild) validation = RNGChild.validate(node, validation);
	return validation;
}



/**
 * Represents an RNG "element" element.
 * @constructor
 * @param {Element} element The RNG element to represent.
 */
mozile.rng.Element = function(element, schema) {
	/**
	 * The element associated with this RNG node.
	 * @private
	 * @type Element
	 */
	this._element = element;

	/**
	 * The schema to which this RNG node belongs.
	 * @private
	 * @type mozile.rng.Schema
	 */
	this._schema = schema;
}
mozile.rng.Element.prototype = new mozile.rng.Node;
mozile.rng.Element.prototype.constructor = mozile.rng.Element;


/**
 * An array of all cached properties. They can be cleared with the reset() method.
 * @private
 * @type Array
 */
mozile.rng.Element.prototype._cached = [this._mustHaveCache, this._mayHaveCache, this._descendantElementsCache, this._type, this._name, this._localName, this._prefix, this._namespace];

/**
 * Gets the full name of the element. Strips leading and trailing whitespace.
 * @type String
 */
mozile.rng.Element.prototype.getName = mozile.rng.getName;

/**
 * Gets the local part of the qualified name of the element.
 * @type String
 */
mozile.rng.Element.prototype.getLocalName = mozile.rng.getLocalName;

/**
 * Gets the prefix part of the qualified name of the element.
 * @type String
 */
mozile.rng.Element.prototype.getPrefix = mozile.rng.getPrefix;

/**
 * Gets the namespace for the element.
 * If the element has an "ns" attribute, that is returned.
 * If the element's name has a prefix, the namespace for that prefix is looked up and returned.
 * The element's ancestor elements are checked for an "ns" attribute.
 * If no namespace is found, null is returned.
 * <a href="http://www.oasis-open.org/committees/relax-ng/spec.html#IDAEBZR">Specification</a>
 * @type String
 */
mozile.rng.Element.prototype.getNamespace = function() {
	if(this._namespace) return this._namespace;
	if(this._element.getAttribute("ns")) this._namespace = this._element.getAttribute("ns");
	else if(this.getPrefix()) this._namespace = mozile.dom.lookupNamespaceURI(this._element, this.getPrefix());
	else {
		var parent = this.getParentNode();
		while(parent && parent._element) {
			if(parent._element.getAttribute("ns")) {
				this._namespace = parent._element.getAttribute("ns");
				break;
			}
			parent = parent.getParentNode();
		}
	}
	if(!this._namespace) this._namespace = null;
	return this._namespace;
}

/**
 * True if this is an element. False otherwise since the element is the basic unit of validation.
 * @param {String|Element} type Either the type of node to be matched or the exact node to be matched.
 * @param {String} name Optional. The name to be matched.
 * @type Boolean
 */
mozile.rng.Element.prototype.mustHave = function(type, name) {
	if(this === type) return true;
	else if(this.getType() == type) {
		if(name && name != this.getName()) return false;
		return true;
	}
	return false;
}

/**
 * True if this is an element. False otherwise since the element is the basic unit of validation.
 * @param {String|Element} type Either the type of node to be matched or the exact node to be matched.
 * @param {String} name Optional. The name to be matched.
 * @type Boolean
 */
mozile.rng.Element.prototype.mayHave = function(type, name) {
	if(this === type) return true;
	else if(this.getType() == type) {
		if(name && name != this.getName()) return false;
		return true;
	}
	return false;
}

/**
 * Validates the node.
 * Operates in stages, first checking that the node itself is valid, then checking the contents of the node.
 * @param {Node} node The node to be validated.
 * @param {mozile.rng.Validation} validation Optional. The validation object for this validation operation.
 * @type mozile.rng.Validation
 */
mozile.rng.Element.prototype.validate = function(node, validation) {
	if(!node) throw Error("mozile.rng.Element.validate() requires a node.");

	if(!validation) validation = new mozile.rng.Validation();

	validation = this._validateElement(node, validation);
	if(!validation.isValid) return validation;

	validation.logMessage(this, node, "Validating element...");
	var result = new mozile.rng.Validation();
	result.setCurrentParent(node);


	result = this._validateChildElements(node, result);
	if(result.isValid) result = this._validateText(node, result);
	if(result.isValid) result = this._validateAttributes(node, result);

	if(result.isValid) result.logMessage(this, node, "Element is valid.");
	else result.logError(this, node, "Element is not valid.");

	validation.append(result);
	validation.setCurrentElement(node);

	return validation;
}

/**
 * Validates the node type, localName, and namespace for the given node, and makes sure that this mozile.rng.Element has child nodes.
 * @private
 * @param {Node} node The node to be validated.
 * @param {mozile.rng.Validation} validation The mozile.rng.Validation object for this validation operation.
 * @type mozile.rng.Validation
 */
mozile.rng.Element.prototype._validateElement = function(node, validation) {
	if(!node) throw Error("mozile.rng.Element._validateElement() requires a node.");
	if(!validation) throw Error("mozile.rng.Element._validateElement() requires an mozile.rng.Validation object.");

	if(node.nodeType != mozile.dom.ELEMENT_NODE) validation.logError(this, node, "Not an element.");
	if(!validation.isValid) return validation;

	if(mozile.dom.getLocalName(node) != this.getLocalName()) validation.logError(this, node, "Names do not match. '"+ mozile.dom.getLocalName(node) +"' != '"+ this.getLocalName() +"'");
	if(!validation.isValid) return validation;

	var ns = this.getNamespace();
	if(ns) {
		if(node.namespaceURI != ns) validation.logError(this, node, "Namespaces do not match. '"+ node.namespaceURI +"' != '"+ ns +"'");
	}
	else {
		if(node.namespaceURI) validation.logError(this, node, "This element has the namespace '"+ node.namespaceURI +"'but the RNG element has no namespace.");
	}
	if(!validation.isValid) return validation;

	return validation;
}

/**
 * Validates nodes in a sequence. 
 * @private
 * @type mozile.rng.Validation
 */
mozile.rng.Element.prototype._validateSequence = mozile.rng.validateSequence;

/**
 * Validates the child elements of this node.
 * If there are no children, each RNG child is checked to see if this is allowed. 
 * An attribute node or a text node will be used as a child, if available.
 * If there are no children at all, the method ensures that the RNG element has a single child element and it is either a "text" or an "empty" element.
 * If there are any children, each child is checked in sequence. While this happens, information about text and attributes will be added to the mozile.rng.Validation object. For that reason, this method must be run before _validateText and _validateAttributes.
 * @private
 * @param {Node} node The node to be validated.
 * @param {mozile.rng.Validation} validation The mozile.rng.Validation object for this validation operation.
 * @type mozile.rng.Validation
 */
mozile.rng.Element.prototype._validateChildElements = function(node, validation) {
	if(!node) throw Error("mozile.rng.Element._validateChildElements() requires a node.");
	if(!validation) throw Error("mozile.rng.Element._validateChildElements() requires an mozile.rng.Validation object.");

	var RNGChildren = this.getChildNodes();
	var child = mozile.dom.getFirstChildElement(node);
	var r=0;
	
	// If there are no child elements, make sure that's acceptable.
	if(!child) {
		for(r=0; r < RNGChildren.length; r++) {
			if(RNGChildren[r].mustHave("element")) {
				validation.logError(this, node, "This element must have child elements, but it does not.");
				return validation;
			}
		}
		if(node.firstChild) validation.logMessage(this, node, "Element has no child elements.");
	}
	
	
	if(!child) {
		if(node.attributes.length > 0) child = node.attributes[0];
		
		// If there are no attributes, make sure that's acceptable.
		else {
			for(r=0; r < RNGChildren.length; r++) {
				if(RNGChildren[r].mustHave("attribute")) {
					validation.logError(this, node, "This element must have attributes, but it does not.");
					return validation;
				}
			}
			//validation.logMessage(this, node, "Element has no attributes.");
		}
	}
	
	if(!child && node.firstChild) {
		child = node.firstChild;
	}

	if(!child) validation.logMessage(this, node, "Element has no child nodes or attributes.");

	// Validate each child element in sequence.
	else {
		validation = this._validateSequence(child, validation);

		if(validation.isValid) {
			var overflow;
			if(mozile.dom.getFirstChildElement(node) && !validation.getCurrentElement())
				overflow = mozile.dom.getFirstChildElement(node);
			if(validation.getCurrentElement() && mozile.dom.getNextSiblingElement(validation.getCurrentElement()) )
				overflow = mozile.dom.getNextSiblingElement(validation.getCurrentElement());
			if(overflow) {
				validation.logError(this, node, "There are elements which are not matched by any RNG rules: '"+ overflow.nodeName +"'.");
		  }
		}

		if(mozile.dom.getFirstChildElement(node) && validation.isValid) validation.logMessage(this, node, "All child elements are valid.");
	}

	return validation;
}

/**
 * Validates the child text nodes for this node.
 * If there are any non-whitespace text nodes, then isEmpty must be false and allowText must be true, otherwise the element is invalid.
 * @private
 * @param {Node} node The node to be validated.
 * @param {mozile.rng.Validation} validation The mozile.rng.Validation object for this validation operation.
 * @type mozile.rng.Validation
 */
mozile.rng.Element.prototype._validateText = function(node, validation) {
	if(!node) throw Error("mozile.rng.Element._validateText() requires a node.");
	if(!validation) throw Error("mozile.rng.Element._validateText() requires an mozile.rng.Validation object.");

	var child;
	for(var r=0; r < node.childNodes.length; r++) {
		child = node.childNodes[r];
		if(child.nodeType != mozile.dom.TEXT_NODE) continue;
		if(mozile.dom.isWhitespace(child)) continue;
		if(validation.isEmpty) validation.logError(this, node, "This element contains text, but it is supposed to be empty.");
		if(!validation.allowText) validation.logError(this, node, "This element contains text but that is not allowed.");
		//else validation.logMessage(this, node, "Text is allowed in this element.");
		if(validation.isValid) {
			validation.logMessage(this, node, "Element has text content.");
			break;
		}
	}
	return validation;
}

/**
 * Validates the attributes for this node.
 * This method expects the validation object to contain a list of valid attributes. If there are any attributes which are not on that list (and are not ignored) then validation fails.
 * @private
 * @param {Node} node The node to be validated.
 * @param {mozile.rng.Validation} validation The mozile.rng.Validation object for this validation operation.
 * @type mozile.rng.Validation
 */
mozile.rng.Element.prototype._validateAttributes = function(node, validation) {
	var attr;
	for(var r=0; r < node.attributes.length; r++) {
		attr = node.attributes[r];
		if(mozile.dom.isIgnored(attr)) continue;
		if(validation.isEmpty) validation.logError(this, node, "This element contains attributes, but it is supposed to be empty.");
		if(validation.isAttributeValid(attr)) continue;
		validation.logError(this, node, "This element contains an invalid attribute: "+ attr.nodeName);
	}
	if(validation.isValid && node.attributes.length > 0) validation.logMessage(this, node, "All attributes are valid.");
	return validation;
}




/**
 * Represents an RNG "attribute" element.
 * @constructor
 * @param {Element} element The RNG element to represent.
 */
mozile.rng.Attribute = function(element, schema) {
	/**
	 * The element associated with this RNG node.
	 * @private
	 * @type Element
	 */
	this._element = element;

	/**
	 * The schema to which this RNG node belongs.
	 * @private
	 * @type mozile.rng.Schema
	 */
	this._schema = schema;
}
mozile.rng.Attribute.prototype = new mozile.rng.Node;
mozile.rng.Attribute.prototype.constructor = mozile.rng.Attribute;


/**
 * An array of all cached properties. They can be cleared with the reset() method.
 * @private
 * @type Array
 */
mozile.rng.Attribute.prototype._cached = [this._mustHaveCache, this._mayHaveCache, this._descendantElementsCache, this._type, this._name, this._localName, this._prefix, this._namespace];

/**
 * Gets the name of the element. Strips leading and trailing whitespace.
 * @type String
 */
mozile.rng.Attribute.prototype.getName = mozile.rng.getName;

/**
 * Gets the local part of the qualified name of the element.
 * @type String
 */
mozile.rng.Attribute.prototype.getLocalName = mozile.rng.getLocalName;

/**
 * Gets the prefix part of the qualified name of the element.
 * @type String
 */
mozile.rng.Attribute.prototype.getPrefix = mozile.rng.getPrefix;

/**
 * Gets the namespace for the element.
 * If the element has an "ns" attribute, that is returned.
 * If the element's name has a prefix, the namespace for that prefix is looked up and returned. The special case where the prefix is "xml" is ignored.
 * If no namespace is found, null is returned.
 * <a href="http://www.oasis-open.org/committees/relax-ng/spec.html#IDAEBZR">Specification</a>
 * @type String
 */
mozile.rng.Attribute.prototype.getNamespace = function() {
	if(this._namespace) return this._namespace;
	if(this._element.getAttribute("ns")) this._namespace = this._element.getAttribute("ns");
	else if(this.getPrefix()) {
		if(this.getPrefix() == "xml") this._namespace = null;
		else this._namespace = mozile.dom.lookupNamespaceURI(this._element, this.getPrefix());
	}
	if(!this._namespace) this._namespace = null;
	return this._namespace;
}

/**
 * Validates this RNG object. Attributes don't need children.
 * @type mozile.rng.Validation
 */
mozile.rng.Attribute.prototype.selfValidate = function(validation) {
	return validation;
}

/**
 * True if this is an attribute. False otherwise.
 * @param {String|Element} type Either the type of node to be matched or the exact node to be matched.
 * @param {String} name Optional. The name to be matched.
 * @type Boolean
 */
mozile.rng.Attribute.prototype.mustHave = function(type, name) {
	if(this === type) return true;
	else if(this.getType() == type) {
		if(name && name != this.getName()) return false;
		return true;
	}
	return false;
}

/**
 * True if this is an attribute. False otherwise.
 * @param {String|Element} type Either the type of node to be matched or the exact node to be matched.
 * @param {String} name Optional. The name to be matched.
 * @type Boolean
 */
mozile.rng.Attribute.prototype.mayHave = function(type, name) {
	if(this === type) return true;
	else if(this.getType() == type) {
		if(name && name != this.getName()) return false;
		return true;
	}
	return false;
}

/**
 * Checks the node's parent for this attribute, and validates the attribute's value.
 * If this element has a namespace, then it looks for an attribute with that namespace and the localName.
 * Otherwise it looks for an attribute with localName.
 * @param {Node} node The node to be validated.
 * @param {mozile.rng.Validation} validation The mozile.rng.Validation object for this validation operation.
 * @type mozile.rng.Validation
 */
mozile.rng.Attribute.prototype.validate = function(node, validation) {
	if(!node) throw Error("mozile.rng.Attribute.validate() requires a node.");
	if(!validation) throw Error("mozile.rng.Attribute.validate() requires an mozile.rng.Validation object.");

	//var element;
	//if(node.ownerElement) element = node.ownerElement;
	//else element = node.parentNode;
	//alert("Validating Attribute "+ this.getName() +" "+ element);
	var element = validation.getCurrentParent();
	if(!element) throw Error("mozile.rng.Attribute.validate() requires the Validation object to have a 'current parent' element.");
	
	if(element) {
		var attr;
		var ns = this.getNamespace();
		if(ns) {
			if(element.getAttributeNodeNS) attr = element.getAttributeNodeNS(ns, this.getLocalName());
			else {
				// IE doesn't support getAttributeNodeNS
				var prefix = mozile.dom.lookupPrefix(element, ns);
				if(prefix) attr = element.getAttributeNode(prefix +":"+ this.getLocalName());
				//alert(prefix+":"+this.getLocalName() +" -> "+ typeof(attr));
			}
		}
		else attr = element.getAttributeNode(this.getLocalName());
		
		// TODO: validate the attribute's value

		if(attr) {
			validation.addAttribute(attr);
			validation.logMessage(this, element, "Attribute "+ attr.nodeName +" validated.");
		}
		else validation.logError(this, element, "Attribute "+ this.getName() +" not found.");
	}
	else validation.logError(this, node, "The node has no parent node.");

	return validation;
}


/**
 * Represents an RNG "text" element.
 * @constructor
 * @param {Element} element The RNG element to represent.
 */
mozile.rng.Text = function(element, schema) {
	/**
	 * The element associated with this RNG node.
	 * @private
	 * @type Element
	 */
	this._element = element;

	/**
	 * The schema to which this RNG node belongs.
	 * @private
	 * @type mozile.rng.Schema
	 */
	this._schema = schema;
}
mozile.rng.Text.prototype = new mozile.rng.Node;
mozile.rng.Text.prototype.constructor = mozile.rng.Text;


/**
 * True if the type is "text". False otherwise since text cannot have child nodes.
 * @param {String|Element} type Either the type of node to be matched or the exact node to be matched.
 * @param {String} name Optional. The name to be matched.
 * @type Boolean
 */
mozile.rng.Text.prototype.mustHave = function(type, name) {
	if(this === type) return true;
	else if(this.getType() == type) return true;
	else return false;
}

/**
 * True if the type is "text". False otherwise since text cannot have child nodes.
 * @param {String|Element} type Either the type of node to be matched or the exact node to be matched.
 * @param {String} name Optional. The name to be matched.
 * @type Boolean
 */
mozile.rng.Text.prototype.mayHave = function(type, name) {
	if(this === type) return true;
	else if(this.getType() == type) return true;
	else return false;
}

/**
 * Validates this RNG object. Text elements are not allowed to have children.
 * @type mozile.rng.Validation
 */
mozile.rng.Text.prototype.selfValidate = function(validation) {
	if(mozile.dom.getFirstChildElement(this._element))
		validation.logError(this, this._element, "This RNG element must not have any child elements.");
	return validation;
}

/**
 * Validates the node.
 * @param {Node} node The node to be validated.
 * @param {mozile.rng.Validation} validation The mozile.rng.Validation object for this validation operation.
 * @type mozile.rng.Validation
 */
mozile.rng.Text.prototype.validate = function(node, validation) {
	if(!node) throw Error("mozile.rng.Text.validate() requires a node.");
	if(!validation) throw Error("mozile.rng.Text.validate() requires an mozile.rng.Validation object.");

	if(!validation.allowText) validation.logMessage(this, node.parentNode, "This element is allowed to contain text.");
	validation.allowText = true;
	return validation;
}



/**
 * Represents an RNG "empty" element.
 * <p>An empty element indicates that the element can have no child nodes of any kind, including attributes and non-whitespace text.
 * <a href="http://www.oasis-open.org/committees/relax-ng/spec.html#empty-pattern">Specification</a>
 * @constructor
 * @param {Element} element The RNG element to represent.
 */
mozile.rng.Empty = function(element, schema) {
	/**
	 * The element associated with this RNG node.
	 * @private
	 * @type Element
	 */
	this._element = element;

	/**
	 * The schema to which this RNG node belongs.
	 * @private
	 * @type mozile.rng.Schema
	 */
	this._schema = schema;
}
mozile.rng.Empty.prototype = new mozile.rng.Node;
mozile.rng.Empty.prototype.constructor = mozile.rng.Empty;

/**
 * True if the type is "empty". False otherwise since empty can't have children.
 * @param {String|Element} type Either the type of node to be matched or the exact node to be matched.
 * @param {String} name Optional. The name to be matched.
 * @type Boolean
 */
mozile.rng.Empty.prototype.mustHave = function(type, name) {
	if(this === type) return true;
	else if(this.getType() == type) return true;
	else return false;
}

/**
 * True if the type is "empty". False otherwise since empty can't have children.
 * @param {String|Element} type Either the type of node to be matched or the exact node to be matched.
 * @param {String} name Optional. The name to be matched.
 * @type Boolean
 */
mozile.rng.Empty.prototype.mayHave = function(type, name) {
	if(this === type) return true;
	else if(this.getType() == type) return true;
	else return false;
}

/**
 * Validates this RNG object. Empty elements are not allowed to have children.
 * @type mozile.rng.Validation
 */
mozile.rng.Empty.prototype.selfValidate = function(validation) {
	if(mozile.dom.getFirstChildElement(this._element))
		validation.logError(this, this._element, "This RNG element must not have any child elements.");
	return validation;
}

/**
 * Validates the node.
 * @param {Node} node The node to be validated.
 * @param {mozile.rng.Validation} validation The mozile.rng.Validation object for this validation operation.
 * @type mozile.rng.Validation
 */
mozile.rng.Empty.prototype.validate = function(node, validation) {
	if(!node) throw Error("mozile.rng.Empty.validate() requires a node.");
	if(!validation) throw Error("mozile.rng.Empty.validate() requires an mozile.rng.Validation object.");
	validation.isEmpty = true;
	validation.logMessage(this, node.parentNode, "This element must be empty.");
	return validation;
}


/**
 * Represents an RNG "group" element.
 * @constructor
 * @param {Element} element The RNG element to represent.
 */
mozile.rng.Group = function(element, schema) {
	/**
	 * The element associated with this RNG node.
	 * @private
	 * @type Element
	 */
	this._element = element;

	/**
	 * The schema to which this RNG node belongs.
	 * @private
	 * @type mozile.rng.Schema
	 */
	this._schema = schema;
}
mozile.rng.Group.prototype = new mozile.rng.Node;
mozile.rng.Group.prototype.constructor = mozile.rng.Group;


/**
 * Validates this RNG object. Groups must have at least two child elements.
 * @type mozile.rng.Validation
 */
mozile.rng.Group.prototype.selfValidate = function(validation) {
	if(mozile.dom.getChildElements(this._element).length < 2)
		validation.logError(this, this._element, "This RNG element must have at least two child elements.");
	return validation;
}

/**
 * Validates nodes in a sequence. 
 * @private
 * @type mozile.rng.Validation
 */
mozile.rng.Group.prototype._validateSequence = mozile.rng.validateSequence;

/**
 * Validates the node.
 * @param {Node} node The node to be validated.
 * @param {mozile.rng.Validation} validation The mozile.rng.Validation object for this validation operation.
 * @type mozile.rng.Validation
 */
mozile.rng.Group.prototype.validate = function(node, validation) {
	if(!node) throw Error("mozile.rng.Group.validate() requires a node.");
	if(!validation) throw Error("mozile.rng.Group.validate() requires an mozile.rng.Validation object.");
	
	validation = this._validateSequence(node, validation);

	if(validation.isValid) validation.logMessage(this, validation.getCurrentElement(), "Group is valid.");	
	return validation;
}



/**
 * Represents an RNG "optional" element.
 * @constructor
 * @param {Element} element The RNG element to represent.
 */
mozile.rng.Optional = function(element, schema) {
	/**
	 * The element associated with this RNG node.
	 * @private
	 * @type Element
	 */
	this._element = element;

	/**
	 * The schema to which this RNG node belongs.
	 * @private
	 * @type mozile.rng.Schema
	 */
	this._schema = schema;
}
mozile.rng.Optional.prototype = new mozile.rng.Node;
mozile.rng.Optional.prototype.constructor = mozile.rng.Optional;

/**
 * True if the type is "optional". False otherwise since any children are optional.
 * @param {String|Element} type Either the type of node to be matched or the exact node to be matched.
 * @param {String} name Optional. The name to be matched.
 * @type Boolean
 */
mozile.rng.Optional.prototype.mustHave = function(type, name) {
	if(this === type) return true;
	else if(this.getType() == type) return true;
	else return false;
}

/**
 * Validates nodes in a sequence. 
 * @private
 * @type mozile.rng.Validation
 */
mozile.rng.Optional.prototype._validateSequence = mozile.rng.validateSequence;

/**
 * Validates the node.
 * Creates a new validation object and tries to validate all children.
 * If the validation succeeds the validation object is merged and returned.
 * If the validation fails, the validation object is returned but not merged.
 * @param {Node} node The node to be validated.
 * @param {mozile.rng.Validation} validation The mozile.rng.Validation object for this validation operation.
 * @type mozile.rng.Validation
 */
mozile.rng.Optional.prototype.validate = function(node, validation) {
	if(!node) throw Error("mozile.rng.Optional.validate() requires a node.");
	if(!validation) throw Error("mozile.rng.Optional.validate() requires an mozile.rng.Validation object.");
	
	var result = validation.branch();
	result = this._validateSequence(node, result);

	if(result.isValid) {
		validation.count = 1;
		validation.merge(result);
		validation.logMessage(this, validation.getCurrentElement(), "Option is present.");
	}
	else {
		if(mozile.rng.debug) validation.append(result, true); 
		validation.logMessage(this, node, "Option is not present.");
	}
	
	return validation;
}


/**
 * Represents an RNG "zeroOrMore" element.
 * @constructor
 * @param {Element} element The RNG element to represent.
 */
mozile.rng.ZeroOrMore = function(element, schema) {
	/**
	 * The element associated with this RNG node.
	 * @private
	 * @type Element
	 */
	this._element = element;

	/**
	 * The schema to which this RNG node belongs.
	 * @private
	 * @type mozile.rng.Schema
	 */
	this._schema = schema;
}
mozile.rng.ZeroOrMore.prototype = new mozile.rng.Node;
mozile.rng.ZeroOrMore.prototype.constructor = mozile.rng.ZeroOrMore;

/**
 * True if the type is "zeroOrMore". False otherwise since any children are optional.
 * @param {String|Element} type Either the type of node to be matched or the exact node to be matched.
 * @param {String} name Optional. The name to be matched.
 * @type Boolean
 */
mozile.rng.ZeroOrMore.prototype.mustHave = function(type, name) {
	if(this === type) return true;
	else if(this.getType() == type) return true;
	else return false;
}

/**
 * Validates the node.
 * @param {Node} node The node to be validated.
 * @param {mozile.rng.Validation} validation The mozile.rng.Validation object for this validation operation.
 * @type mozile.rng.Validation
 */
mozile.rng.ZeroOrMore.prototype.validate = function(node, validation) {
	if(!node) throw Error("mozile.rng.ZeroOrMore.validate() requires a node.");
	if(!validation) throw Error("mozile.rng.ZeroOrMore.validate() requires an mozile.rng.Validation object.");
	
	validation = this._validateMany(node, validation);
	validation.logMessage(this, validation.getCurrentElement(), "ZeroOrMore matched "+ validation.count +" times.");

	return validation;
}

/**
 * Validates nodes in a sequence. 
 * @private
 * @type mozile.rng.Validation
 */
mozile.rng.ZeroOrMore.prototype._validateSequence = mozile.rng.validateSequence;

/**
 * Validates the set of the RNG children as many times as possible. 
 * @private
 * @type mozile.rng.Validation
 */
mozile.rng.ZeroOrMore.prototype._validateMany = mozile.rng.validateMany;


/**
 * Represents an RNG "oneOrMore" element.
 * @constructor
 * @param {Element} element The RNG element to represent.
 */
mozile.rng.OneOrMore = function(element, schema) {
	/**
	 * The element associated with this RNG node.
	 * @private
	 * @type Element
	 */
	this._element = element;

	/**
	 * The schema to which this RNG node belongs.
	 * @private
	 * @type mozile.rng.Schema
	 */
	this._schema = schema;
}
mozile.rng.OneOrMore.prototype = new mozile.rng.Node;
mozile.rng.OneOrMore.prototype.constructor = mozile.rng.OneOrMore;

/**
 * Validates the node.
 * @param {Node} node The node to be validated.
 * @param {mozile.rng.Validation} validation The mozile.rng.Validation object for this validation operation.
 * @type mozile.rng.Validation
 */
mozile.rng.OneOrMore.prototype.validate = function(node, validation) {
	if(!node) throw Error("mozile.rng.OneOrMore.validate() requires a node.");
	if(!validation) throw Error("mozile.rng.OneOrMore.validate() requires an mozile.rng.Validation object.");

	validation = this._validateMany(node, validation);
	if(validation.count == 0) validation.logError(this, validation.getCurrentElement(), "OneOrMore did not match any nodes.");
	validation.logMessage(this, validation.getCurrentElement(), "OneOrMore matched "+ validation.count +" times.");

	return validation;
}

/**
 * Validates nodes in a sequence. 
 * @private
 * @type mozile.rng.Validation
 */
mozile.rng.OneOrMore.prototype._validateSequence = mozile.rng.validateSequence;

/**
 * Validates the set of the RNG children as many times as possible. 
 * @private
 * @type mozile.rng.Validation
 */
mozile.rng.OneOrMore.prototype._validateMany = mozile.rng.validateMany;



/**
 * Represents an RNG "choice" element.
 * @constructor
 * @param {Element} element The RNG element to represent.
 */
mozile.rng.Choice = function(element, schema) {
	/**
	 * The element associated with this RNG node.
	 * @private
	 * @type Element
	 */
	this._element = element;

	/**
	 * The schema to which this RNG node belongs.
	 * @private
	 * @type mozile.rng.Schema
	 */
	this._schema = schema;
}
mozile.rng.Choice.prototype = new mozile.rng.Node;
mozile.rng.Choice.prototype.constructor = mozile.rng.Choice;

/**
 * True if the type is "choice" or if all children mustHave the given type.
 * @param {String|Element} type Either the type of node to be matched or the exact node to be matched.
 * @param {String} name Optional. The name to be matched.
 * @type Boolean
 */
mozile.rng.Choice.prototype.mustHave = function(type, name) {
	if(this === type) return true;
	else if(this.getType() == type) return true;

	var key = this.generateKey(type, name);
	if(!this._mustHaveCache) this._mustHaveCache = new Object();
	if(this._mustHaveCache[key]) return this._mustHaveCache[key];

	var result = true;
	for(var c=0; c < this.getChildNodes().length; c++) {
		if(!this.getChildNodes()[c].mustHave(type, name)) {
			result = false;
			break;	
		}
	}

	this._mustHaveCache[key] = result;
	return result;
}

/**
 * Validates this RNG object. Choices must have at least two child elements.
 * @type mozile.rng.Validation
 */
mozile.rng.Choice.prototype.selfValidate = function(validation) {
	if(mozile.dom.getChildElements(this._element).length < 2)
		validation.logError(this, this._element, "This RNG element must have at least two child elements.");
	return validation;
}

/**
 * Validates the node.
 * If this allowText is false and this choice contains an mozile.rng.Text child, then the mozile.rng.Text is immediately validated. Afterward any mozile.rng.Text children are ignored when trying to match an element.
 * For each RNG child object a new mozile.rng.Validation object is created and the node is validated. The first valid result is returned.
 * @param {Node} node The node to be validated.
 * @param {mozile.rng.Validation} validation The mozile.rng.Validation object for this validation operation.
 * @type mozile.rng.Validation
 */
mozile.rng.Choice.prototype.validate = function(node, validation) {
	if(!node) throw Error("mozile.rng.Choice.validate() requires a node.");
	if(!validation) throw Error("mozile.rng.Choice.validate() requires an mozile.rng.Validation object.");

	var RNGChildren = this.getChildNodes();
	var r = 0;
	for(r=0; r < RNGChildren.length; r++) {
		if(validation.allowText) break;
		if(RNGChildren[r].getType() == "text") validation = RNGChildren[r].validate(node, validation);
	}
	
	var result;
	for(r=0; r < RNGChildren.length; r++) {
		if(RNGChildren[r].getType() == "text" && node.nodeType == mozile.dom.ELEMENT_NODE) continue;
		result = validation.branch();
		result = RNGChildren[r].validate(node, result);

		if(result.isValid) break;
		else if(mozile.rng.debug) validation.append(result, true); 
	}

	if(result && result.isValid) {
		validation.merge(result);
		validation.logMessage(this, validation.getCurrentElement(), "Choice number "+ (r+1) +" selected.");
	}
	else validation.logError(this, node, "All choices failed to validate.");

	return validation;
}



/**
 * Represents an RNG "define" element.
 * @constructor
 * @param {Element} element The RNG element to represent.
 */
mozile.rng.Define = function(element, schema) {
	/**
	 * The element associated with this RNG node.
	 * @private
	 * @type Element
	 */
	this._element = element;

	/**
	 * The schema to which this RNG node belongs.
	 * @private
	 * @type mozile.rng.Schema
	 */
	this._schema = schema;
}
mozile.rng.Define.prototype = new mozile.rng.Node;
mozile.rng.Define.prototype.constructor = mozile.rng.Define;


/**
 * Gets the full name of the element. Strips leading and trailing whitespace.
 * @type String
 */
mozile.rng.Define.prototype.getName = mozile.rng.getName;

/**
 * Combines this mozile.rng.Define object with another mozile.rng.Define object.
 * @type mozile.rng.Define
 */
mozile.rng.Define.prototype.combine = mozile.rng.combine;

/**
 * Validates this RNG object. Definitions must bust be children of grammar, include, or div elements, and have child elements and names.
 * @type mozile.rng.Validation
 */
mozile.rng.Define.prototype.selfValidate = function(validation) {
	if(!this._element.parentNode)
		validation.logError(this, this._element, "This RNG element must be the child of a 'grammar', 'include', or 'div' element.");

	var parentName = mozile.dom.getLocalName(this._element.parentNode);
	if (parentName != "grammar" && parentName != "include" && parentName != "div")
		validation.logError(this, this._element, "This RNG element must be the child of a 'grammar', 'include', or 'div' element.");

	if(!mozile.dom.getFirstChildElement(this._element))
		validation.logError(this, this._element, "This RNG element must have at least one child element.");
	if(!this.getName())
		validation.logError(this, this._element, "This RNG element must have a 'name' attribute.");
	
	var combine = this._element.getAttribute("combine");
	if(combine) {
		if(combine != "choice" && combine != "interleave")
			validation.logError(this, this._element, "This RNG 'define' element has an invalid 'combine' attribute value of '"+ combine +"'.");
	}

	return validation;
}

/**
 * Validates nodes in a sequence. 
 * @private
 * @type mozile.rng.Validation
 */
mozile.rng.Define.prototype._validateSequence = mozile.rng.validateSequence;

/**
 * Validates the node.
 * Validates each node in sequence.
 * @param {Node} node The node to be validated.
 * @param {mozile.rng.Validation} validation The mozile.rng.Validation object for this validation operation.
 * @type mozile.rng.Validation
 */
mozile.rng.Define.prototype.validate = function(node, validation) {
	if(!node) throw Error("mozile.rng.Define.validate() requires a node.");
	if(!validation) throw Error("mozile.rng.Define.validate() requires an mozile.rng.Validation object.");
	
	validation = this._validateSequence(node, validation);

	if(validation.isValid) validation.logMessage(this, validation.getCurrentElement(), "Definition is valid.");	
	return validation;
}



/**
 * Represents an RNG "ref" element.
 * @constructor
 * @param {Element} element The RNG element to represent.
 */
mozile.rng.Ref = function(element, schema) {
	/**
	 * The element associated with this RNG node.
	 * @private
	 * @type Element
	 */
	this._element = element;

	/**
	 * The schema to which this RNG node belongs.
	 * @private
	 * @type mozile.rng.Schema
	 */
	this._schema = schema;
}
mozile.rng.Ref.prototype = new mozile.rng.Node;
mozile.rng.Ref.prototype.constructor = mozile.rng.Ref;

/**
 * An array of all cached properties. They can be cleared with the reset() method.
 * @private
 * @type Array
 */
mozile.rng.Ref.prototype._cached = [this._type, this._name, this._grammar, this._definition];

/**
 * Gets the full name of the element. Strips leading and trailing whitespace.
 * @type String
 */
mozile.rng.Ref.prototype.getName = mozile.rng.getName;

/**
 * Gets the definition matching this reference. Caches values for faster lookup.
 * @type mozile.rng.Define
 */
mozile.rng.Ref.prototype.getDefinition = function() {
	if(!this._grammar) this._grammar = this.getGrammar()
	if(this._grammar) {
		if(!this._definition) this._definition = this.getGrammar().getDefinition(this.getName());
		if(this._definition) return this._definition;
	}
	return null;
}

/**
 * Gets an array of all the elements which descend from this node.
 * @param types The RNG element type(s) of search for. Can be a string or an array of strings. E.g. "element".
 * @param {Boolean} deep Optional. If the current node is an element and "deep" is true, then its children are included.
 * @type Array
 */
mozile.rng.Ref.prototype.getDescendants = function(types, deep) {
	if(!deep && mozile.rng.checkType(types, this.getType())) return new Array(this);
	else if(this.getDefinition()) return this.getDefinition().getDescendants(types, deep);
	else return new Array();
}

/**
 * True if the type is "ref". Otherwise the definition is checked.
 * @param {String|Element} type Either the type of node to be matched or the exact node to be matched.
 * @param {String} name Optional. The name to be matched.
 * @type Boolean
 */
mozile.rng.Ref.prototype.mustHave = function(type, name) {
	if(this === type) return true;
	else if(this.getType() == type) {
		if(name && name != this.getName()) return false;
		return true;
	}
	if(this.getDefinition()) return this.getDefinition().mustHave(type, name);
	else return false;
}

/**
 * True if the type is "ref". Otherwise the definition is checked.
 * @param {String|Element} type Either the type of node to be matched or the exact node to be matched.
 * @param {String} name Optional. The name to be matched.
 * @type Boolean
 */
mozile.rng.Ref.prototype.mayHave = function(type, name) {
	if(this === type) return true;
	else if(this.getType() == type) {
		if(name && name != this.getName()) return false;
		return true;
	}
	if(this.getDefinition()) return this.getDefinition().mayHave(type, name);
	else return false;
}

/**
 * Validates this RNG object. Refs may not have children and they must have names.
 * @type mozile.rng.Validation
 */
mozile.rng.Ref.prototype.selfValidate = function(validation) {
	if(mozile.dom.getFirstChildElement(this._element))
		validation.logError(this, this._element, "This RNG element must not have any child elements.");
	if(!this.getName())
		validation.logError(this, this._element, "This RNG element must have a 'name' attribute.");
	return validation;
}

/**
 * Validates the node.
 * Checks that this reference has a definiton, and calls the definition's validation method.
 * @param {Node} node The node to be validated.
 * @param {mozile.rng.Validation} validation The mozile.rng.Validation object for this validation operation.
 * @type mozile.rng.Validation
 */
mozile.rng.Ref.prototype.validate = function(node, validation) {
	if(this.getDefinition()) {
		validation.logMessage(this, node, "Reference followed.");
		validation = this.getDefinition().validate(node, validation);
	}
	else validation.logError(this, node, "This reference does not have a matching definition.");
	return validation;
}



/**
 * Represents an RNG "data" element.
 * @constructor
 * @param {Element} element The RNG element to represent.
 */
mozile.rng.Data = function(element, schema) {
	/**
	 * The element associated with this RNG node.
	 * @private
	 * @type Element
	 */
	this._element = element;

	/**
	 * The schema to which this RNG node belongs.
	 * @private
	 * @type mozile.rng.Schema
	 */
	this._schema = schema;
}
mozile.rng.Data.prototype = new mozile.rng.Node;
mozile.rng.Data.prototype.constructor = mozile.rng.Data;

/**
 * Validates this RNG object. Data elements do not need children but they need a 'type'.
 * @type mozile.rng.Validation
 */
mozile.rng.Data.prototype.selfValidate = function(validation) {
	if(!this._element.getAttribute("type"))
		validation.logError(this, this._element, "This RNG element must have an 'type' attribute.");
	return validation;
}

/**
 * Returns the name of the datatype.
 * @type String
 */
mozile.rng.Data.prototype.getDataType = function() {
	return this._element.getAttribute("type");
}



/**
 * Represents an RNG "param" element.
 * @constructor
 * @param {Element} element The RNG element to represent.
 */
mozile.rng.Param = function(element, schema) {
	/**
	 * The element associated with this RNG node.
	 * @private
	 * @type Element
	 */
	this._element = element;

	/**
	 * The schema to which this RNG node belongs.
	 * @private
	 * @type mozile.rng.Schema
	 */
	this._schema = schema;
}
mozile.rng.Param.prototype = new mozile.rng.Node;
mozile.rng.Param.prototype.constructor = mozile.rng.Param;

/**
 * Validates this RNG object. Param  elements require children(s) or a text node.
 * @type mozile.rng.Validation
 */
mozile.rng.Param.prototype.selfValidate = function(validation) {
	if(!mozile.dom.getFirstChildElement(this._element) && (this._element.firstChild.nodeValue == ''))
		validation.logError(this, this._element, "This RNG element must contain child elements or text");
	return validation;
}


/**
 * Represents an RNG "value" element.
 * @constructor
 * @param {Element} element The RNG element to represent.
 */
mozile.rng.Value = function(element, schema) {
	/**
	 * The element associated with this RNG node.
	 * @private
	 * @type Element
	 */
	this._element = element;

	/**
	 * The schema to which this RNG node belongs.
	 * @private
	 * @type mozile.rng.Schema
	 */
	this._schema = schema;
}
mozile.rng.Value.prototype = new mozile.rng.Node;
mozile.rng.Value.prototype.constructor = mozile.rng.Value;

/**
 * Validates this RNG object. Value elements must have non-whitespace text.
 * @type mozile.rng.Validation
 */
mozile.rng.Value.prototype.selfValidate = function(validation) {
	for(var r=0; r < this._element.childNodes.length; r++) {
		if(this._element.childNodes[r].nodeType == mozile.dom.TEXT_NODE &&
			!mozile.dom.isWhitespace(this._element.childNodes[r])) {
			return validation;
		}	
	}

	validation.logError(this, this._element, "This RNG element must contain non-whitespace text.");
	return validation;
}

/**
 * Returns the text contents of this node.
 * @type String
 */
mozile.rng.Value.prototype.getValue = function() {
	return mozile.dom.getText(this._element);
}


/**
 * Represents an RNG "include" element.
 * @constructor
 * @param {Element} element The RNG element to represent.
 */
mozile.rng.Include = function(element, schema) {
	/**
	 * The element associated with this RNG node.
	 * @private
	 * @type Element
	 */
	this._element = element;

	/**
	 * The schema to which this RNG node belongs.
	 * @private
	 * @type mozile.rng.Schema
	 */
	this._schema = schema;
}
mozile.rng.Include.prototype = new mozile.rng.Node;
mozile.rng.Include.prototype.constructor = mozile.rng.Include;

/**
 * Validates this RNG object. Includes must be childen of a grammar. They do not need children but they need an 'href'.
 * @type mozile.rng.Validation
 */
mozile.rng.Include.prototype.selfValidate = function(validation) {
	if(!this._element.parentNode || mozile.dom.getLocalName(this._element.parentNode) != "grammar")
		validation.logError(this, this._element, "This RNG element must be the child of a grammar element.");

	if(!this._element.getAttribute("href"))
		validation.logError(this, this._element, "This RNG element must have an 'href' attribute.");
	return validation;
}

/**
 * Includes definitions from the target grammar into the current grammar.
 * Converts a relative href to a URI relative to the element's owner document, if necessary.
 * Loads the file for the included schema and makes sure its document element is a "grammar" element.
 * Tries to include all "define", "start", and "include" elements from the top level of the new file into this object's grammar.
 * The "define" elements can be overridden by children of this Include object.
 * @param {mozile.rng.Validation} validation The mozile.rng.Validation object for this validation operation.
 * @type mozile.rng.Validation
 */
mozile.rng.Include.prototype.include = function(validation) {
	if(!validation) throw Error("mozile.rng.Include.include() requires an mozile.rng.Validation object.");

	var href = this._element.getAttribute("href");
	var schema = this.getSchema();
	var grammar = this.getGrammar();
	if(!href || !schema || !grammar) {
		validation.logError(this, this._element, "mozile.rng.Include.include() requires an 'href', a schema, and a grammar, but one or more was missing.");
		return validation;
	}
	
	// Get an absolute URI.
	var root;
	if(this.filepath) root = this.filepath;
	else if(schema.filepath) root = schema.filepath;
	//else if(mozile.root) root = mozile.root;
	var URI = mozile.getAbsolutePath(href, root);
	var filepath = mozile.getDirectory(URI);
	//mozile.debug.debug("mozile.rng.Include.include", root +" "+ href +" "+ URI);
	//alert(root +" "+ href +" "+ URI);
	
	// Load the file.
	validation.logMessage(this, this._element, "Including RNG content from '"+ URI +"'.");
	var rngDoc = schema.load(URI);
	if(!rngDoc || !rngDoc.documentElement || 
		mozile.dom.getLocalName(rngDoc.documentElement) != "grammar") {
		validation.logError(this, null, "mozile.rng.Include.include() could not load schema at '"+ URI +"'.");
		//throw Error("Bad URI: "+ URI);
		return validation;
	}


	// Include the children of the new file.
	var children = rngDoc.documentElement.childNodes;
	for(var c=0; c < children.length; c++) {
		var child = null;
		switch(mozile.dom.getLocalName(children[c])) {
			case "start":
				child = schema.parseElement(children[c], validation);
				break;
			case "define":
				// Check to see if the definition should be overridden.
				for(var i=0; i < this._element.childNodes.length; i++) {
					var includeChild = this._element.childNodes[i];
					if(includeChild.nodeType != mozile.dom.ELEMENT_NODE) continue;
					var includeChildName = includeChild.getAttribute("name");
					if(mozile.dom.getLocalName(includeChild) == "define" &&
						includeChildName == children[c].getAttribute("name") ) {
						validation.logMessage(this, includeChild, "Overriding defintition of '"+ includeChildName +"'.");
						child = schema.parseElement(includeChild, validation);
						break;
					}
				}
				if(!child) child = schema.parseElement(children[c], validation);
				break;
			case "include":	
				child = schema.parseElement(children[c], validation);
				child.filepath = filepath;
				break;
			default:
				child = null;
				break;
		}
		if(child) grammar.appendChild(child);
	}
	return validation;
}



/**
 * Represents an RNG "interleave" element.
 * @constructor
 * @param {Element} element The RNG element to represent.
 */
mozile.rng.Interleave = function(element, schema) {
	/**
	 * The element associated with this RNG node.
	 * @private
	 * @type Element
	 */
	this._element = element;

	/**
	 * The schema to which this RNG node belongs.
	 * @private
	 * @type mozile.rng.Schema
	 */
	this._schema = schema;
}
mozile.rng.Interleave.prototype = new mozile.rng.Node;
mozile.rng.Interleave.prototype.constructor = mozile.rng.Interleave;

/**
 * Validates this RNG object. Groups must have at least two child elements.
 * @type mozile.rng.Validation
 */
mozile.rng.Interleave.prototype.selfValidate = function(validation) {
	if(mozile.dom.getChildElements(this._element).length < 2)
		validation.logError(this, this._element, "This RNG element must have at least two child elements.");
	return validation;
}

/**
 * Validates the node.
 * @type mozile.rng.Validation
 */
mozile.rng.Interleave.prototype.validate = mozile.rng.validateInterleave;






/**
 * Represents an RNG "div" element.
 * @constructor
 * @param {Element} element The RNG element to represent.
 */
mozile.rng.Div = function(element, schema) {
	/**
	 * The element associated with this RNG node.
	 * @private
	 * @type Element
	 */
	this._element = element;

	/**
	 * The schema to which this RNG node belongs.
	 * @private
	 * @type mozile.rng.Schema
	 */
	this._schema = schema;
}
mozile.rng.Div.prototype = new mozile.rng.Node;
mozile.rng.Div.prototype.constructor = mozile.rng.Div;


/**
 * Validates this RNG object. Divs must bust be children of a grammar.
 * @type mozile.rng.Validation
 */
mozile.rng.Div.prototype.selfValidate = function(validation) {
	if(!this._element.parentNode || 
		mozile.dom.getLocalName(this._element.parentNode) != "grammar" )
		validation.logError(this, this._element, "This RNG element must be the child of a 'grammar' element.");
	return validation;
}

/**
 * Validates nodes in a sequence. 
 * @private
 * @type mozile.rng.Validation
 */
mozile.rng.Div.prototype._validateSequence = mozile.rng.validateSequence;

/**
 * Validates the node.
 * @param {Node} node The node to be validated.
 * @param {mozile.rng.Validation} validation The mozile.rng.Validation object for this validation operation.
 * @type mozile.rng.Validation
 */
mozile.rng.Div.prototype.validate = function(node, validation) {
	if(!node) throw Error("mozile.rng.Div.validate() requires a node.");
	if(!validation) throw Error("mozile.rng.Div.validate() requires an mozile.rng.Validation object.");
	
	validation = this._validateSequence(node, validation);

	if(validation.isValid) validation.logMessage(this, validation.getCurrentElement(), "Div is valid.");	
	return validation;
}




/*
mozile.rng.Mixed
mozile.rng.ParentRef
mozile.rng.ExternalRef
mozile.rng.List
mozile.rng.NotAllowed
mozile.rng.Except
mozile.rng.NSName
mozile.rng.Div
*/


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