JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
9643e0d7ca56ab705c81bf9d31072e6254e8585b
[ckeditor.git] / _source / core / htmlparser / element.js
1 /*\r
2 Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.\r
3 For licensing, see LICENSE.html or http://ckeditor.com/license\r
4 */\r
5 \r
6 /**\r
7  * A lightweight representation of an HTML element.\r
8  * @param {String} name The element name.\r
9  * @param {Object} attributes And object holding all attributes defined for\r
10  *              this element.\r
11  * @constructor\r
12  * @example\r
13  */\r
14 CKEDITOR.htmlParser.element = function( name, attributes )\r
15 {\r
16         /**\r
17          * The element name.\r
18          * @type String\r
19          * @example\r
20          */\r
21         this.name = name;\r
22 \r
23         /**\r
24          * Holds the attributes defined for this element.\r
25          * @type Object\r
26          * @example\r
27          */\r
28         this.attributes = attributes || {};\r
29 \r
30         /**\r
31          * The nodes that are direct children of this element.\r
32          * @type Array\r
33          * @example\r
34          */\r
35         this.children = [];\r
36 \r
37         // Reveal the real semantic of our internal custom tag name (#6639),\r
38         // when resolving whether it's block like.\r
39         var realName = name || '',\r
40                 prefixed = realName.match( /^cke:(.*)/ );\r
41         prefixed && ( realName = prefixed[ 1 ] );\r
42 \r
43         var isBlockLike = !!( CKEDITOR.dtd.$nonBodyContent[ realName ]\r
44                                 || CKEDITOR.dtd.$block[ realName ]\r
45                                 || CKEDITOR.dtd.$listItem[ realName ]\r
46                                 || CKEDITOR.dtd.$tableContent[ realName ]\r
47                                 || CKEDITOR.dtd.$nonEditable[ realName ]\r
48                                 || realName == 'br' );\r
49 \r
50         this.isEmpty    = !!CKEDITOR.dtd.$empty[ name ];\r
51         this.isUnknown  = !CKEDITOR.dtd[ name ];\r
52 \r
53         /** @private */\r
54         this._ =\r
55         {\r
56                 isBlockLike : isBlockLike,\r
57                 hasInlineStarted : this.isEmpty || !isBlockLike\r
58         };\r
59 };\r
60 \r
61 /**\r
62  *  Object presentation of  CSS style declaration text.\r
63  *  @param {CKEDITOR.htmlParser.element|String} elementOrStyleText A html parser element or the inline style text.\r
64  */\r
65 CKEDITOR.htmlParser.cssStyle = function()\r
66 {\r
67          var styleText,\r
68                 arg = arguments[ 0 ],\r
69                 rules = {};\r
70 \r
71         styleText = arg instanceof CKEDITOR.htmlParser.element ? arg.attributes.style : arg;\r
72 \r
73         // html-encoded quote might be introduced by 'font-family'\r
74         // from MS-Word which confused the following regexp. e.g.\r
75         //'font-family: "Lucida, Console"'\r
76         ( styleText || '' )\r
77                 .replace( /"/g, '"' )\r
78                 .replace( /\s*([^ :;]+)\s*:\s*([^;]+)\s*(?=;|$)/g,\r
79                         function( match, name, value )\r
80                         {\r
81                                 name == 'font-family' && ( value = value.replace( /["']/g, '' ) );\r
82                                 rules[ name.toLowerCase() ] = value;\r
83                         });\r
84 \r
85         return {\r
86 \r
87                 rules : rules,\r
88 \r
89                 /**\r
90                  *  Apply the styles onto the specified element or object.\r
91                  * @param {CKEDITOR.htmlParser.element|CKEDITOR.dom.element|Object} obj\r
92                  */\r
93                 populate : function( obj )\r
94                 {\r
95                         var style = this.toString();\r
96                         if ( style )\r
97                         {\r
98                                 obj instanceof CKEDITOR.dom.element ?\r
99                                         obj.setAttribute( 'style', style ) :\r
100                                         obj instanceof CKEDITOR.htmlParser.element ?\r
101                                                 obj.attributes.style = style :\r
102                                                 obj.style = style;\r
103                         }\r
104                 },\r
105 \r
106                 toString : function()\r
107                 {\r
108                         var output = [];\r
109                         for ( var i in rules )\r
110                                 rules[ i ] && output.push( i, ':', rules[ i ], ';' );\r
111                         return output.join( '' );\r
112                 }\r
113         };\r
114 };\r
115 \r
116 (function()\r
117 {\r
118         // Used to sort attribute entries in an array, where the first element of\r
119         // each object is the attribute name.\r
120         var sortAttribs = function( a, b )\r
121         {\r
122                 a = a[0];\r
123                 b = b[0];\r
124                 return a < b ? -1 : a > b ? 1 : 0;\r
125         };\r
126 \r
127         CKEDITOR.htmlParser.element.prototype =\r
128         {\r
129                 /**\r
130                  * The node type. This is a constant value set to {@link CKEDITOR.NODE_ELEMENT}.\r
131                  * @type Number\r
132                  * @example\r
133                  */\r
134                 type : CKEDITOR.NODE_ELEMENT,\r
135 \r
136                 /**\r
137                  * Adds a node to the element children list.\r
138                  * @param {Object} node The node to be added. It can be any of of the\r
139                  *              following types: {@link CKEDITOR.htmlParser.element},\r
140                  *              {@link CKEDITOR.htmlParser.text} and\r
141                  *              {@link CKEDITOR.htmlParser.comment}.\r
142                  * @function\r
143                  * @example\r
144                  */\r
145                 add : CKEDITOR.htmlParser.fragment.prototype.add,\r
146 \r
147                 /**\r
148                  * Clone this element.\r
149                  * @returns {CKEDITOR.htmlParser.element} The element clone.\r
150                  * @example\r
151                  */\r
152                 clone : function()\r
153                 {\r
154                         return new CKEDITOR.htmlParser.element( this.name, this.attributes );\r
155                 },\r
156 \r
157                 /**\r
158                  * Writes the element HTML to a CKEDITOR.htmlWriter.\r
159                  * @param {CKEDITOR.htmlWriter} writer The writer to which write the HTML.\r
160                  * @example\r
161                  */\r
162                 writeHtml : function( writer, filter )\r
163                 {\r
164                         var attributes = this.attributes;\r
165 \r
166                         // Ignore cke: prefixes when writing HTML.\r
167                         var element = this,\r
168                                 writeName = element.name,\r
169                                 a, newAttrName, value;\r
170 \r
171                         var isChildrenFiltered;\r
172 \r
173                         /**\r
174                          * Providing an option for bottom-up filtering order ( element\r
175                          * children to be pre-filtered before the element itself ).\r
176                          */\r
177                         element.filterChildren = function()\r
178                         {\r
179                                 if ( !isChildrenFiltered )\r
180                                 {\r
181                                         var writer = new CKEDITOR.htmlParser.basicWriter();\r
182                                         CKEDITOR.htmlParser.fragment.prototype.writeChildrenHtml.call( element, writer, filter );\r
183                                         element.children = new CKEDITOR.htmlParser.fragment.fromHtml( writer.getHtml(), 0, element.clone() ).children;\r
184                                         isChildrenFiltered = 1;\r
185                                 }\r
186                         };\r
187 \r
188                         if ( filter )\r
189                         {\r
190                                 while ( true )\r
191                                 {\r
192                                         if ( !( writeName = filter.onElementName( writeName ) ) )\r
193                                                 return;\r
194 \r
195                                         element.name = writeName;\r
196 \r
197                                         if ( !( element = filter.onElement( element ) ) )\r
198                                                 return;\r
199 \r
200                                         element.parent = this.parent;\r
201 \r
202                                         if ( element.name == writeName )\r
203                                                 break;\r
204 \r
205                                         // If the element has been replaced with something of a\r
206                                         // different type, then make the replacement write itself.\r
207                                         if ( element.type != CKEDITOR.NODE_ELEMENT )\r
208                                         {\r
209                                                 element.writeHtml( writer, filter );\r
210                                                 return;\r
211                                         }\r
212 \r
213                                         writeName = element.name;\r
214 \r
215                                         // This indicate that the element has been dropped by\r
216                                         // filter but not the children.\r
217                                         if ( !writeName )\r
218                                         {\r
219                                                 // Fix broken parent refs.\r
220                                                 for ( var c = 0, length = this.children.length ; c < length ; c++ )\r
221                                                         this.children[ c ].parent = element.parent;\r
222 \r
223                                                 this.writeChildrenHtml.call( element, writer, isChildrenFiltered ? null : filter );\r
224                                                 return;\r
225                                         }\r
226                                 }\r
227 \r
228                                 // The element may have been changed, so update the local\r
229                                 // references.\r
230                                 attributes = element.attributes;\r
231                         }\r
232 \r
233                         // Open element tag.\r
234                         writer.openTag( writeName, attributes );\r
235 \r
236                         // Copy all attributes to an array.\r
237                         var attribsArray = [];\r
238                         // Iterate over the attributes twice since filters may alter\r
239                         // other attributes.\r
240                         for ( var i = 0 ; i < 2; i++ )\r
241                         {\r
242                                 for ( a in attributes )\r
243                                 {\r
244                                         newAttrName = a;\r
245                                         value = attributes[ a ];\r
246                                         if ( i == 1 )\r
247                                                 attribsArray.push( [ a, value ] );\r
248                                         else if ( filter )\r
249                                         {\r
250                                                 while ( true )\r
251                                                 {\r
252                                                         if ( !( newAttrName = filter.onAttributeName( a ) ) )\r
253                                                         {\r
254                                                                 delete attributes[ a ];\r
255                                                                 break;\r
256                                                         }\r
257                                                         else if ( newAttrName != a )\r
258                                                         {\r
259                                                                 delete attributes[ a ];\r
260                                                                 a = newAttrName;\r
261                                                                 continue;\r
262                                                         }\r
263                                                         else\r
264                                                                 break;\r
265                                                 }\r
266                                                 if ( newAttrName )\r
267                                                 {\r
268                                                         if ( ( value = filter.onAttribute( element, newAttrName, value ) ) === false )\r
269                                                                 delete attributes[ newAttrName ];\r
270                                                         else\r
271                                                                 attributes [ newAttrName ] = value;\r
272                                                 }\r
273                                         }\r
274                                 }\r
275                         }\r
276                         // Sort the attributes by name.\r
277                         if ( writer.sortAttributes )\r
278                                 attribsArray.sort( sortAttribs );\r
279 \r
280                         // Send the attributes.\r
281                         var len = attribsArray.length;\r
282                         for ( i = 0 ; i < len ; i++ )\r
283                         {\r
284                                 var attrib = attribsArray[ i ];\r
285                                 writer.attribute( attrib[0], attrib[1] );\r
286                         }\r
287 \r
288                         // Close the tag.\r
289                         writer.openTagClose( writeName, element.isEmpty );\r
290 \r
291                         if ( !element.isEmpty )\r
292                         {\r
293                                 this.writeChildrenHtml.call( element, writer, isChildrenFiltered ? null : filter );\r
294                                 // Close the element.\r
295                                 writer.closeTag( writeName );\r
296                         }\r
297                 },\r
298 \r
299                 writeChildrenHtml : function( writer, filter )\r
300                 {\r
301                         // Send children.\r
302                         CKEDITOR.htmlParser.fragment.prototype.writeChildrenHtml.apply( this, arguments );\r
303 \r
304                 }\r
305         };\r
306 })();\r