a1f16e2a9e36a8806ed7758dce9ff2979c44543f
[ckeditor.git] / _source / core / htmlparser / filter.js
1 /*
2 Copyright (c) 2003-2011, 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 */