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