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