]>
git.immae.eu Git - perso/Immae/Projets/packagist/connexionswing-ckeditor-component.git/blob - sources/core/htmlparser/element.js
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
9 * A lightweight representation of an HTML element.
12 * @extends CKEDITOR.htmlParser.node
13 * @constructor Creates an element class instance.
14 * @param {String} name The element name.
15 * @param {Object} attributes An object storing all attributes defined for
18 CKEDITOR
.htmlParser
.element = function( name
, attributes
) {
27 * Stores the attributes defined for this element.
31 this.attributes
= attributes
|| {};
34 * The nodes that are direct children of this element.
38 // Reveal the real semantic of our internal custom tag name (#6639),
39 // when resolving whether it's block like.
40 var realName
= name
|| '',
41 prefixed
= realName
.match( /^cke:(.*)/ );
42 prefixed
&& ( realName
= prefixed
[ 1 ] );
44 var isBlockLike
= !!( CKEDITOR
.dtd
.$nonBodyContent
[ realName
] || CKEDITOR
.dtd
.$block
[ realName
] ||
45 CKEDITOR
.dtd
.$listItem
[ realName
] || CKEDITOR
.dtd
.$tableContent
[ realName
] ||
46 CKEDITOR
.dtd
.$nonEditable
[ realName
] || realName
== 'br' );
48 this.isEmpty
= !!CKEDITOR
.dtd
.$empty
[ name
];
49 this.isUnknown
= !CKEDITOR
.dtd
[ name
];
53 isBlockLike: isBlockLike
,
54 hasInlineStarted: this.isEmpty
|| !isBlockLike
59 * Object presentation of CSS style declaration text.
62 * @constructor Creates a `cssStyle` class instance.
63 * @param {CKEDITOR.htmlParser.element/String} elementOrStyleText
64 * An HTML parser element or the inline style text.
66 CKEDITOR
.htmlParser
.cssStyle = function() {
71 styleText
= arg
instanceof CKEDITOR
.htmlParser
.element
? arg
.attributes
.style : arg
;
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 // TODO reuse CSS methods from tools.
77 ( styleText
|| '' ).replace( /"/g, '"' ).replace( /\s
*([^ :;]+)\s
*:\s
*([^;]+)\s
*(?=;|$)/g
, function( match
, name
, value
) {
78 name
== 'font-family' && ( value
= value
.replace( /["']/g, '' ) );
79 rules
[ name
.toLowerCase() ] = value
;
87 * Applies the styles to the specified element or object.
89 * @param {CKEDITOR.htmlParser.element/CKEDITOR.dom.element/Object} obj
91 populate: function( obj
) {
92 var style
= this.toString();
94 obj
instanceof CKEDITOR
.dom
.element
? obj
.setAttribute( 'style', style
) : obj
instanceof CKEDITOR
.htmlParser
.element
? obj
.attributes
.style
= style : obj
.style
= style
;
99 * Serializes CSS style declaration to a string.
103 toString: function() {
105 for ( var i
in rules
)
106 rules
[ i
] && output
.push( i
, ':', rules
[ i
], ';' );
107 return output
.join( '' );
112 /** @class CKEDITOR.htmlParser.element */
114 // Used to sort attribute entries in an array, where the first element of
115 // each object is the attribute name.
116 var sortAttribs = function( a
, b
) {
119 return a
< b
? -1 : a
> b
? 1 : 0;
121 fragProto
= CKEDITOR
.htmlParser
.fragment
.prototype;
123 CKEDITOR
.htmlParser
.element
.prototype = CKEDITOR
.tools
.extend( new CKEDITOR
.htmlParser
.node(), {
125 * The node type. This is a constant value set to {@link CKEDITOR#NODE_ELEMENT}.
128 * @property {Number} [=CKEDITOR.NODE_ELEMENT]
130 type: CKEDITOR
.NODE_ELEMENT
,
133 * Adds a node to the element children list.
136 * @param {CKEDITOR.htmlParser.node} node The node to be added.
137 * @param {Number} [index] From where the insertion happens.
142 * Clones this element.
144 * @returns {CKEDITOR.htmlParser.element} The element clone.
147 return new CKEDITOR
.htmlParser
.element( this.name
, this.attributes
);
151 * Filters this element and its children with the given filter.
154 * @param {CKEDITOR.htmlParser.filter} filter
155 * @returns {Boolean} The method returns `false` when this element has
156 * been removed or replaced with another. This information means that
157 * {@link #filterChildren} has to repeat the filter on the current
158 * position in parent's children array.
160 filter: function( filter
, context
) {
164 context
= element
.getFilterContext( context
);
166 // Do not process elements with data-cke-processor attribute set to off.
170 // Filtering if it's the root node.
171 if ( !element
.parent
)
172 filter
.onRoot( context
, element
);
175 originalName
= element
.name
;
177 if ( !( name
= filter
.onElementName( context
, originalName
) ) ) {
184 if ( !( element
= filter
.onElement( context
, element
) ) ) {
189 // New element has been returned - replace current one
190 // and process it (stop processing this and return false, what
191 // means that element has been removed).
192 if ( element
!== this ) {
193 this.replaceWith( element
);
197 // If name has been changed - continue loop, so in next iteration
198 // filters for new name will be applied to this element.
199 // If name hasn't been changed - stop.
200 if ( element
.name
== originalName
)
203 // If element has been replaced with something of a
204 // different type, then make the replacement filter itself.
205 if ( element
.type
!= CKEDITOR
.NODE_ELEMENT
) {
206 this.replaceWith( element
);
210 // This indicate that the element has been dropped by
211 // filter but not the children.
212 if ( !element
.name
) {
213 this.replaceWithChildren();
218 var attributes
= element
.attributes
,
219 a
, value
, newAttrName
;
221 for ( a
in attributes
) {
223 value
= attributes
[ a
];
225 // Loop until name isn't modified.
226 // A little bit senseless, but IE would do that anyway
227 // because it iterates with for-in loop even over properties
228 // created during its run.
230 if ( !( newAttrName
= filter
.onAttributeName( context
, a
) ) ) {
231 delete attributes
[ a
];
233 } else if ( newAttrName
!= a
) {
234 delete attributes
[ a
];
243 if ( ( value
= filter
.onAttribute( context
, element
, newAttrName
, value
) ) === false )
244 delete attributes
[ newAttrName
];
246 attributes
[ newAttrName
] = value
;
250 if ( !element
.isEmpty
)
251 this.filterChildren( filter
, false, context
);
257 * Filters this element's children with the given filter.
259 * Element's children may only be filtered once by one
260 * instance of the filter.
262 * @method filterChildren
263 * @param {CKEDITOR.htmlParser.filter} filter
265 filterChildren: fragProto
.filterChildren
,
268 * Writes the element HTML to the CKEDITOR.htmlWriter.
270 * @param {CKEDITOR.htmlParser.basicWriter} writer The writer to which HTML will be written.
271 * @param {CKEDITOR.htmlParser.filter} [filter] The filter to be applied to this node.
272 * **Note:** It is unsafe to filter an offline (not appended) node.
274 writeHtml: function( writer
, filter
) {
276 this.filter( filter
);
278 var name
= this.name
,
280 attributes
= this.attributes
,
285 writer
.openTag( name
, attributes
);
287 // Copy all attributes to an array.
288 for ( attrName
in attributes
)
289 attribsArray
.push( [ attrName
, attributes
[ attrName
] ] );
291 // Sort the attributes by name.
292 if ( writer
.sortAttributes
)
293 attribsArray
.sort( sortAttribs
);
295 // Send the attributes.
296 for ( i
= 0, l
= attribsArray
.length
; i
< l
; i
++ ) {
297 attr
= attribsArray
[ i
];
298 writer
.attribute( attr
[ 0 ], attr
[ 1 ] );
302 writer
.openTagClose( name
, this.isEmpty
);
304 this.writeChildrenHtml( writer
);
306 // Close the element.
308 writer
.closeTag( name
);
312 * Sends children of this element to the writer.
314 * @param {CKEDITOR.htmlParser.basicWriter} writer The writer to which HTML will be written.
315 * @param {CKEDITOR.htmlParser.filter} [filter]
317 writeChildrenHtml: fragProto
.writeChildrenHtml
,
320 * Replaces this element with its children.
324 replaceWithChildren: function() {
325 var children
= this.children
;
327 for ( var i
= children
.length
; i
; )
328 children
[ --i
].insertAfter( this );
334 * Executes a callback on each node (of the given type) in this element.
336 * // Create a <p> element with foo<b>bar</b>bom as its content.
337 * var elP = CKEDITOR.htmlParser.fragment.fromHtml( 'foo<b>bar</b>bom', 'p' );
338 * elP.forEach( function( node ) {
339 * console.log( node );
342 * // 1. document fragment,
344 * // 3. "foo" text node,
346 * // 5. "bar" text node,
347 * // 6. "bom" text node.
350 * @param {Function} callback Function to be executed on every node.
351 * **Since 4.3**: If `callback` returned `false`, the descendants of the current node will be ignored.
352 * @param {CKEDITOR.htmlParser.node} callback.node Node passed as an argument.
353 * @param {Number} [type] Whether the specified `callback` will be executed only on nodes of this type.
354 * @param {Boolean} [skipRoot] Do not execute `callback` on this element.
356 forEach: fragProto
.forEach
,
359 * Gets this element's first child. If `condition` is given, this method returns
360 * the first child which satisfies that condition.
363 * @param {String/Object/Function} condition Name of a child, a hash of names, or a validator function.
364 * @returns {CKEDITOR.htmlParser.node}
366 getFirst: function( condition
) {
368 return this.children
.length
? this.children
[ 0 ] : null;
370 if ( typeof condition
!= 'function' )
371 condition
= nameCondition( condition
);
373 for ( var i
= 0, l
= this.children
.length
; i
< l
; ++i
) {
374 if ( condition( this.children
[ i
] ) )
375 return this.children
[ i
];
381 * Gets this element's inner HTML.
386 getHtml: function() {
387 var writer
= new CKEDITOR
.htmlParser
.basicWriter();
388 this.writeChildrenHtml( writer
);
389 return writer
.getHtml();
393 * Sets this element's inner HTML.
396 * @param {String} html
398 setHtml: function( html
) {
399 var children
= this.children
= CKEDITOR
.htmlParser
.fragment
.fromHtml( html
).children
;
401 for ( var i
= 0, l
= children
.length
; i
< l
; ++i
)
402 children
[ i
].parent
= this;
406 * Gets this element's outer HTML.
411 getOuterHtml: function() {
412 var writer
= new CKEDITOR
.htmlParser
.basicWriter();
413 this.writeHtml( writer
);
414 return writer
.getHtml();
418 * Splits this element at the given index.
421 * @param {Number} index Index at which the element will be split — `0` means the beginning,
422 * `1` after first child node, etc.
423 * @returns {CKEDITOR.htmlParser.element} The new element following this one.
425 split: function( index
) {
426 var cloneChildren
= this.children
.splice( index
, this.children
.length
- index
),
427 clone
= this.clone();
429 for ( var i
= 0; i
< cloneChildren
.length
; ++i
)
430 cloneChildren
[ i
].parent
= clone
;
432 clone
.children
= cloneChildren
;
434 if ( cloneChildren
[ 0 ] )
435 cloneChildren
[ 0 ].previous
= null;
438 this.children
[ index
- 1 ].next
= null;
440 this.parent
.add( clone
, this.getIndex() + 1 );
446 * Adds a class name to the list of classes.
449 * @param {String} className The class name to be added.
451 addClass: function( className
) {
452 if ( this.hasClass( className
) )
455 var c
= this.attributes
[ 'class' ] || '';
457 this.attributes
[ 'class' ] = c
+ ( c
? ' ' : '' ) + className
;
461 * Removes a class name from the list of classes.
464 * @param {String} className The class name to be removed.
466 removeClass: function( className
) {
467 var classes
= this.attributes
[ 'class' ];
472 // We can safely assume that className won't break regexp.
473 // http://stackoverflow.com/questions/448981/what-characters-are-valid-in-css-class-names
474 classes
= CKEDITOR
.tools
.trim( classes
.replace( new RegExp( '(?:\\s+|^)' + className
+ '(?:\\s+|$)' ), ' ' ) );
477 this.attributes
[ 'class' ] = classes
;
479 delete this.attributes
[ 'class' ];
483 * Checkes whether this element has a class name.
486 * @param {String} className The class name to be checked.
487 * @returns {Boolean} Whether this element has a `className`.
489 hasClass: function( className
) {
490 var classes
= this.attributes
[ 'class' ];
495 return ( new RegExp( '(?:^|\\s)' + className
+ '(?=\\s|$)' ) ).test( classes
);
498 getFilterContext: function( ctx
) {
505 nestedEditable: false
509 if ( !ctx
.off
&& this.attributes
[ 'data-cke-processor' ] == 'off' )
510 changes
.push( 'off', true );
512 if ( !ctx
.nonEditable
&& this.attributes
.contenteditable
== 'false' )
513 changes
.push( 'nonEditable', true );
514 // A context to be given nestedEditable must be nonEditable first (by inheritance) (#11372, #11698).
515 // Special case: #11504 - filter starts on <body contenteditable=true>,
516 // so ctx.nonEditable has not been yet set to true.
517 else if ( ctx
.nonEditable
&& !ctx
.nestedEditable
&& this.attributes
.contenteditable
== 'true' )
518 changes
.push( 'nestedEditable', true );
520 if ( changes
.length
) {
521 ctx
= CKEDITOR
.tools
.copy( ctx
);
522 for ( var i
= 0; i
< changes
.length
; i
+= 2 )
523 ctx
[ changes
[ i
] ] = changes
[ i
+ 1 ];
530 function nameCondition( condition
) {
531 return function( el
) {
532 return el
.type
== CKEDITOR
.NODE_ELEMENT
&&
533 ( typeof condition
== 'string' ? el
.name
== condition : el
.name
in condition
);