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