text: ﻿/*
  Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
  For licensing, see LICENSE.html or http://ckeditor.com/license
  */
  
  (function()
  {
  	CKEDITOR.htmlParser.filter = CKEDITOR.tools.createClass(
  	{
  		$ : function( rules )
  		{
  			this._ =
  			{
  				elementNames : [],
  				attributeNames : [],
  				elements : { $length : 0 },
  				attributes : { $length : 0 }
  			};
  
  			if ( rules )
  				this.addRules( rules, 10 );
  		},
  
  		proto :
  		{
  			addRules : function( rules, priority )
  			{
  				if ( typeof priority != 'number' )
  					priority = 10;
  
  				// Add the elementNames.
  				addItemsToList( this._.elementNames, rules.elementNames, priority );
  
  				// Add the attributeNames.
  				addItemsToList( this._.attributeNames, rules.attributeNames, priority );
  
  				// Add the elements.
  				addNamedItems( this._.elements, rules.elements, priority );
  
  				// Add the attributes.
  				addNamedItems( this._.attributes, rules.attributes, priority );
  
  				// Add the text.
  				this._.text = transformNamedItem( this._.text, rules.text, priority ) || this._.text;
  
  				// Add the comment.
  				this._.comment = transformNamedItem( this._.comment, rules.comment, priority ) || this._.comment;
  
  				// Add root fragment.
  				this._.root = transformNamedItem( this._.root, rules.root, priority ) || this._.root;
  			},
  
  			onElementName : function( name )
  			{
  				return filterName( name, this._.elementNames );
  			},
  
  			onAttributeName : function( name )
  			{
  				return filterName( name, this._.attributeNames );
  			},
  
  			onText : function( text )
  			{
  				var textFilter = this._.text;
  				return textFilter ? textFilter.filter( text ) : text;
  			},
  
  			onComment : function( commentText, comment )
  			{
  				var textFilter = this._.comment;
  				return textFilter ? textFilter.filter( commentText, comment ) : commentText;
  			},
  
  			onFragment : function( element )
  			{
  				var rootFilter = this._.root;
  				return rootFilter ? rootFilter.filter( element ) : element;
  			},
  
  			onElement : function( element )
  			{
  				// We must apply filters set to the specific element name as
  				// well as those set to the generic $ name. So, add both to an
  				// array and process them in a small loop.
  				var filters = [ this._.elements[ '^' ], this._.elements[ element.name ], this._.elements.$ ],
  					filter, ret;
  
  				for ( var i = 0 ; i < 3 ; i++ )
  				{
  					filter = filters[ i ];
  					if ( filter )
  					{
  						ret = filter.filter( element, this );
  
  						if ( ret === false )
  							return null;
  
  						if ( ret && ret != element )
  							return this.onNode( ret );
  
  						// The non-root element has been dismissed by one of the filters.
  						if ( element.parent && !element.name )
  							break;
  					}
  				}
  
  				return element;
  			},
  
  			onNode : function( node )
  			{
  				var type = node.type;
  
  				return type == CKEDITOR.NODE_ELEMENT ? this.onElement( node ) :
  					type == CKEDITOR.NODE_TEXT ? new CKEDITOR.htmlParser.text( this.onText( node.value ) ) :
  					type == CKEDITOR.NODE_COMMENT ? new CKEDITOR.htmlParser.comment( this.onComment( node.value ) ):
  					null;
  			},
  
  			onAttribute : function( element, name, value )
  			{
  				var filter = this._.attributes[ name ];
  
  				if ( filter )
  				{
  					var ret = filter.filter( value, element, this );
  
  					if ( ret === false )
  						return false;
  
  					if ( typeof ret != 'undefined' )
  						return ret;
  				}
  
  				return value;
  			}
  		}
  	});
  
  	function filterName( name, filters )
  	{
  		for ( var i = 0 ; name && i < filters.length ; i++ )
  		{
  			var filter = filters[ i ];
  			name = name.replace( filter[ 0 ], filter[ 1 ] );
  		}
  		return name;
  	}
  
  	function addItemsToList( list, items, priority )
  	{
  		if ( typeof items == 'function' )
  			items = [ items ];
  
  		var i, j,
  			listLength = list.length,
  			itemsLength = items && items.length;
  
  		if ( itemsLength )
  		{
  			// Find the index to insert the items at.
  			for ( i = 0 ; i < listLength && list[ i ].pri < priority ; i++ )
  			{ /*jsl:pass*/ }
  
  			// Add all new items to the list at the specific index.
  			for ( j = itemsLength - 1 ; j >= 0 ; j-- )
  			{
  				var item = items[ j ];
  				if ( item )
  				{
  					item.pri = priority;
  					list.splice( i, 0, item );
  				}
  			}
  		}
  	}
  
  	function addNamedItems( hashTable, items, priority )
  	{
  		if ( items )
  		{
  			for ( var name in items )
  			{
  				var current = hashTable[ name ];
  
  				hashTable[ name ] =
  					transformNamedItem(
  						current,
  						items[ name ],
  						priority );
  
  				if ( !current )
  					hashTable.$length++;
  			}
  		}
  	}
  
  	function transformNamedItem( current, item, priority )
  	{
  		if ( item )
  		{
  			item.pri = priority;
  
  			if ( current )
  			{
  				// If the current item is not an Array, transform it.
  				if ( !current.splice )
  				{
  					if ( current.pri > priority )
  						current = [ item, current ];
  					else
  						current = [ current, item ];
  
  					current.filter = callItems;
  				}
  				else
  					addItemsToList( current, item, priority );
  
  				return current;
  			}
  			else
  			{
  				item.filter = item;
  				return item;
  			}
  		}
  	}
  
  	// Invoke filters sequentially on the array, break the iteration
  	// when it doesn't make sense to continue anymore.
  	function callItems( currentEntry )
  	{
  		var isNode = currentEntry.type
  			|| currentEntry instanceof CKEDITOR.htmlParser.fragment;
  
  		for ( var i = 0 ; i < this.length ; i++ )
  		{
  			// Backup the node info before filtering.
  			if ( isNode )
  			{
  				var orgType = currentEntry.type,
  						orgName = currentEntry.name;
  			}
  
  			var item = this[ i ],
  				ret = item.apply( window, arguments );
  
  			if ( ret === false )
  				return ret;
  
  			// We're filtering node (element/fragment).
  			if ( isNode )
  			{
  				// No further filtering if it's not anymore
  				// fitable for the subsequent filters.
  				if ( ret && ( ret.name != orgName
  					|| ret.type != orgType ) )
  				{
  					return ret;
  				}
  			}
  			// Filtering value (nodeName/textValue/attrValue).
  			else
  			{
  				// No further filtering if it's not
  				// any more values.
  				if ( typeof ret != 'string' )
  					return ret;
  			}
  
  			ret != undefined && ( currentEntry = ret );
  		}
  
  		return currentEntry;
  	}
  })();
  
  // "entities" plugin
  /*
  {
  	text : function( text )
  	{
  		// TODO : Process entities.
  		return text.toUpperCase();
  	}
  };
  */
  