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