1 /*
  2 Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved.
  3 For licensing, see LICENSE.html or http://ckeditor.com/license
  4 */
  5 
  6 (function()
  7 {
  8 	CKEDITOR.htmlParser.filter = CKEDITOR.tools.createClass(
  9 	{
 10 		$ : function( rules )
 11 		{
 12 			this._ =
 13 			{
 14 				elementNames : [],
 15 				attributeNames : [],
 16 				elements : { $length : 0 },
 17 				attributes : { $length : 0 }
 18 			};
 19 
 20 			if ( rules )
 21 				this.addRules( rules, 10 );
 22 		},
 23 
 24 		proto :
 25 		{
 26 			addRules : function( rules, priority )
 27 			{
 28 				if ( typeof priority != 'number' )
 29 					priority = 10;
 30 
 31 				// Add the elementNames.
 32 				addItemsToList( this._.elementNames, rules.elementNames, priority );
 33 
 34 				// Add the attributeNames.
 35 				addItemsToList( this._.attributeNames, rules.attributeNames, priority );
 36 
 37 				// Add the elements.
 38 				addNamedItems( this._.elements, rules.elements, priority );
 39 
 40 				// Add the attributes.
 41 				addNamedItems( this._.attributes, rules.attributes, priority );
 42 
 43 				// Add the text.
 44 				this._.text = transformNamedItem( this._.text, rules.text, priority ) || this._.text;
 45 
 46 				// Add the comment.
 47 				this._.comment = transformNamedItem( this._.comment, rules.comment, priority ) || this._.comment;
 48 
 49 				// Add root fragment.
 50 				this._.root = transformNamedItem( this._.root, rules.root, priority ) || this._.root;
 51 			},
 52 
 53 			onElementName : function( name )
 54 			{
 55 				return filterName( name, this._.elementNames );
 56 			},
 57 
 58 			onAttributeName : function( name )
 59 			{
 60 				return filterName( name, this._.attributeNames );
 61 			},
 62 
 63 			onText : function( text )
 64 			{
 65 				var textFilter = this._.text;
 66 				return textFilter ? textFilter.filter( text ) : text;
 67 			},
 68 
 69 			onComment : function( commentText, comment )
 70 			{
 71 				var textFilter = this._.comment;
 72 				return textFilter ? textFilter.filter( commentText, comment ) : commentText;
 73 			},
 74 
 75 			onFragment : function( element )
 76 			{
 77 				var rootFilter = this._.root;
 78 				return rootFilter ? rootFilter.filter( element ) : element;
 79 			},
 80 
 81 			onElement : function( element )
 82 			{
 83 				// We must apply filters set to the specific element name as
 84 				// well as those set to the generic $ name. So, add both to an
 85 				// array and process them in a small loop.
 86 				var filters = [ this._.elements[ '^' ], this._.elements[ element.name ], this._.elements.$ ],
 87 					filter, ret;
 88 
 89 				for ( var i = 0 ; i < 3 ; i++ )
 90 				{
 91 					filter = filters[ i ];
 92 					if ( filter )
 93 					{
 94 						ret = filter.filter( element, this );
 95 
 96 						if ( ret === false )
 97 							return null;
 98 
 99 						if ( ret && ret != element )
100 							return this.onNode( ret );
101 
102 						// The non-root element has been dismissed by one of the filters.
103 						if ( element.parent && !element.name )
104 							break;
105 					}
106 				}
107 
108 				return element;
109 			},
110 
111 			onNode : function( node )
112 			{
113 				var type = node.type;
114 
115 				return type == CKEDITOR.NODE_ELEMENT ? this.onElement( node ) :
116 					type == CKEDITOR.NODE_TEXT ? new CKEDITOR.htmlParser.text( this.onText( node.value ) ) :
117 					type == CKEDITOR.NODE_COMMENT ? new CKEDITOR.htmlParser.comment( this.onComment( node.value ) ):
118 					null;
119 			},
120 
121 			onAttribute : function( element, name, value )
122 			{
123 				var filter = this._.attributes[ name ];
124 
125 				if ( filter )
126 				{
127 					var ret = filter.filter( value, element, this );
128 
129 					if ( ret === false )
130 						return false;
131 
132 					if ( typeof ret != 'undefined' )
133 						return ret;
134 				}
135 
136 				return value;
137 			}
138 		}
139 	});
140 
141 	function filterName( name, filters )
142 	{
143 		for ( var i = 0 ; name && i < filters.length ; i++ )
144 		{
145 			var filter = filters[ i ];
146 			name = name.replace( filter[ 0 ], filter[ 1 ] );
147 		}
148 		return name;
149 	}
150 
151 	function addItemsToList( list, items, priority )
152 	{
153 		if ( typeof items == 'function' )
154 			items = [ items ];
155 
156 		var i, j,
157 			listLength = list.length,
158 			itemsLength = items && items.length;
159 
160 		if ( itemsLength )
161 		{
162 			// Find the index to insert the items at.
163 			for ( i = 0 ; i < listLength && list[ i ].pri < priority ; i++ )
164 			{ /*jsl:pass*/ }
165 
166 			// Add all new items to the list at the specific index.
167 			for ( j = itemsLength - 1 ; j >= 0 ; j-- )
168 			{
169 				var item = items[ j ];
170 				if ( item )
171 				{
172 					item.pri = priority;
173 					list.splice( i, 0, item );
174 				}
175 			}
176 		}
177 	}
178 
179 	function addNamedItems( hashTable, items, priority )
180 	{
181 		if ( items )
182 		{
183 			for ( var name in items )
184 			{
185 				var current = hashTable[ name ];
186 
187 				hashTable[ name ] =
188 					transformNamedItem(
189 						current,
190 						items[ name ],
191 						priority );
192 
193 				if ( !current )
194 					hashTable.$length++;
195 			}
196 		}
197 	}
198 
199 	function transformNamedItem( current, item, priority )
200 	{
201 		if ( item )
202 		{
203 			item.pri = priority;
204 
205 			if ( current )
206 			{
207 				// If the current item is not an Array, transform it.
208 				if ( !current.splice )
209 				{
210 					if ( current.pri > priority )
211 						current = [ item, current ];
212 					else
213 						current = [ current, item ];
214 
215 					current.filter = callItems;
216 				}
217 				else
218 					addItemsToList( current, item, priority );
219 
220 				return current;
221 			}
222 			else
223 			{
224 				item.filter = item;
225 				return item;
226 			}
227 		}
228 	}
229 
230 	// Invoke filters sequentially on the array, break the iteration
231 	// when it doesn't make sense to continue anymore.
232 	function callItems( currentEntry )
233 	{
234 		var isNode = currentEntry.type
235 			|| currentEntry instanceof CKEDITOR.htmlParser.fragment;
236 
237 		for ( var i = 0 ; i < this.length ; i++ )
238 		{
239 			// Backup the node info before filtering.
240 			if ( isNode )
241 			{
242 				var orgType = currentEntry.type,
243 						orgName = currentEntry.name;
244 			}
245 
246 			var item = this[ i ],
247 				ret = item.apply( window, arguments );
248 
249 			if ( ret === false )
250 				return ret;
251 
252 			// We're filtering node (element/fragment).
253 			if ( isNode )
254 			{
255 				// No further filtering if it's not anymore
256 				// fitable for the subsequent filters.
257 				if ( ret && ( ret.name != orgName
258 					|| ret.type != orgType ) )
259 				{
260 					return ret;
261 				}
262 			}
263 			// Filtering value (nodeName/textValue/attrValue).
264 			else
265 			{
266 				// No further filtering if it's not
267 				// any more values.
268 				if ( typeof ret != 'string' )
269 					return ret;
270 			}
271 
272 			ret != undefined && ( currentEntry = ret );
273 		}
274 
275 		return currentEntry;
276 	}
277 })();
278 
279 // "entities" plugin
280 /*
281 {
282 	text : function( text )
283 	{
284 		// TODO : Process entities.
285 		return text.toUpperCase();
286 	}
287 };
288 */
289