new ckeditor
New ckeditor
This commit is contained in:
152
ckeditor/core/htmlparser/basicwriter.js
Normal file
152
ckeditor/core/htmlparser/basicwriter.js
Normal file
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @class
|
||||
* @todo
|
||||
*/
|
||||
CKEDITOR.htmlParser.basicWriter = CKEDITOR.tools.createClass( {
|
||||
/**
|
||||
* Creates a basicWriter class instance.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
$: function() {
|
||||
this._ = {
|
||||
output: []
|
||||
};
|
||||
},
|
||||
|
||||
proto: {
|
||||
/**
|
||||
* Writes the tag opening part for a opener tag.
|
||||
*
|
||||
* // Writes '<p'.
|
||||
* writer.openTag( 'p', { class : 'MyClass', id : 'MyId' } );
|
||||
*
|
||||
* @param {String} tagName The element name for this tag.
|
||||
* @param {Object} attributes The attributes defined for this tag. The
|
||||
* attributes could be used to inspect the tag.
|
||||
*/
|
||||
openTag: function( tagName ) {
|
||||
this._.output.push( '<', tagName );
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes the tag closing part for a opener tag.
|
||||
*
|
||||
* // Writes '>'.
|
||||
* writer.openTagClose( 'p', false );
|
||||
*
|
||||
* // Writes ' />'.
|
||||
* writer.openTagClose( 'br', true );
|
||||
*
|
||||
* @param {String} tagName The element name for this tag.
|
||||
* @param {Boolean} isSelfClose Indicates that this is a self-closing tag,
|
||||
* like `<br>` or `<img>`.
|
||||
*/
|
||||
openTagClose: function( tagName, isSelfClose ) {
|
||||
if ( isSelfClose )
|
||||
this._.output.push( ' />' );
|
||||
else
|
||||
this._.output.push( '>' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes an attribute. This function should be called after opening the
|
||||
* tag with {@link #openTagClose}.
|
||||
*
|
||||
* // Writes ' class="MyClass"'.
|
||||
* writer.attribute( 'class', 'MyClass' );
|
||||
*
|
||||
* @param {String} attName The attribute name.
|
||||
* @param {String} attValue The attribute value.
|
||||
*/
|
||||
attribute: function( attName, attValue ) {
|
||||
// Browsers don't always escape special character in attribute values. (#4683, #4719).
|
||||
if ( typeof attValue == 'string' )
|
||||
attValue = CKEDITOR.tools.htmlEncodeAttr( attValue );
|
||||
|
||||
this._.output.push( ' ', attName, '="', attValue, '"' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes a closer tag.
|
||||
*
|
||||
* // Writes '</p>'.
|
||||
* writer.closeTag( 'p' );
|
||||
*
|
||||
* @param {String} tagName The element name for this tag.
|
||||
*/
|
||||
closeTag: function( tagName ) {
|
||||
this._.output.push( '</', tagName, '>' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes text.
|
||||
*
|
||||
* // Writes 'Hello Word'.
|
||||
* writer.text( 'Hello Word' );
|
||||
*
|
||||
* @param {String} text The text value.
|
||||
*/
|
||||
text: function( text ) {
|
||||
this._.output.push( text );
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes a comment.
|
||||
*
|
||||
* // Writes '<!-- My comment -->'.
|
||||
* writer.comment( ' My comment ' );
|
||||
*
|
||||
* @param {String} comment The comment text.
|
||||
*/
|
||||
comment: function( comment ) {
|
||||
this._.output.push( '<!--', comment, '-->' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes any kind of data to the ouput.
|
||||
*
|
||||
* writer.write( 'This is an <b>example</b>.' );
|
||||
*
|
||||
* @param {String} data
|
||||
*/
|
||||
write: function( data ) {
|
||||
this._.output.push( data );
|
||||
},
|
||||
|
||||
/**
|
||||
* Empties the current output buffer.
|
||||
*
|
||||
* writer.reset();
|
||||
*/
|
||||
reset: function() {
|
||||
this._.output = [];
|
||||
this._.indent = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Empties the current output buffer.
|
||||
*
|
||||
* var html = writer.getHtml();
|
||||
*
|
||||
* @param {Boolean} reset Indicates that the {@link #reset} method is to
|
||||
* be automatically called after retrieving the HTML.
|
||||
* @returns {String} The HTML written to the writer so far.
|
||||
*/
|
||||
getHtml: function( reset ) {
|
||||
var html = this._.output.join( '' );
|
||||
|
||||
if ( reset )
|
||||
this.reset();
|
||||
|
||||
return html;
|
||||
}
|
||||
}
|
||||
} );
|
||||
48
ckeditor/core/htmlparser/cdata.js
Normal file
48
ckeditor/core/htmlparser/cdata.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
( function() {
|
||||
|
||||
/**
|
||||
* A lightweight representation of HTML CDATA.
|
||||
*
|
||||
* @class
|
||||
* @extends CKEDITOR.htmlParser.node
|
||||
* @constructor Creates a cdata class instance.
|
||||
* @param {String} value The CDATA section value.
|
||||
*/
|
||||
CKEDITOR.htmlParser.cdata = function( value ) {
|
||||
/**
|
||||
* The CDATA value.
|
||||
*
|
||||
* @property {String}
|
||||
*/
|
||||
this.value = value;
|
||||
};
|
||||
|
||||
CKEDITOR.htmlParser.cdata.prototype = CKEDITOR.tools.extend( new CKEDITOR.htmlParser.node(), {
|
||||
/**
|
||||
* CDATA has the same type as {@link CKEDITOR.htmlParser.text} This is
|
||||
* a constant value set to {@link CKEDITOR#NODE_TEXT}.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=CKEDITOR.NODE_TEXT]
|
||||
*/
|
||||
type: CKEDITOR.NODE_TEXT,
|
||||
|
||||
filter: function() {},
|
||||
|
||||
/**
|
||||
* Writes the CDATA with no special manipulations.
|
||||
*
|
||||
* @param {CKEDITOR.htmlParser.basicWriter} writer The writer to which write the HTML.
|
||||
*/
|
||||
writeHtml: function( writer ) {
|
||||
writer.write( this.value );
|
||||
}
|
||||
} );
|
||||
} )();
|
||||
80
ckeditor/core/htmlparser/comment.js
Normal file
80
ckeditor/core/htmlparser/comment.js
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* A lightweight representation of an HTML comment.
|
||||
*
|
||||
* @class
|
||||
* @extends CKEDITOR.htmlParser.node
|
||||
* @constructor Creates a comment class instance.
|
||||
* @param {String} value The comment text value.
|
||||
*/
|
||||
CKEDITOR.htmlParser.comment = function( value ) {
|
||||
/**
|
||||
* The comment text.
|
||||
*
|
||||
* @property {String}
|
||||
*/
|
||||
this.value = value;
|
||||
|
||||
/** @private */
|
||||
this._ = {
|
||||
isBlockLike: false
|
||||
};
|
||||
};
|
||||
|
||||
CKEDITOR.htmlParser.comment.prototype = CKEDITOR.tools.extend( new CKEDITOR.htmlParser.node(), {
|
||||
/**
|
||||
* The node type. This is a constant value set to {@link CKEDITOR#NODE_COMMENT}.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=CKEDITOR.NODE_COMMENT]
|
||||
*/
|
||||
type: CKEDITOR.NODE_COMMENT,
|
||||
|
||||
/**
|
||||
* Filter this comment with given filter.
|
||||
*
|
||||
* @since 4.1
|
||||
* @param {CKEDITOR.htmlParser.filter} filter
|
||||
* @returns {Boolean} Method returns `false` when this comment has
|
||||
* been removed or replaced with other node. This is an information for
|
||||
* {@link CKEDITOR.htmlParser.element#filterChildren} that it has
|
||||
* to repeat filter on current position in parent's children array.
|
||||
*/
|
||||
filter: function( filter, context ) {
|
||||
var comment = this.value;
|
||||
|
||||
if ( !( comment = filter.onComment( context, comment, this ) ) ) {
|
||||
this.remove();
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( typeof comment != 'string' ) {
|
||||
this.replaceWith( comment );
|
||||
return false;
|
||||
}
|
||||
|
||||
this.value = comment;
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes the HTML representation of this comment to a CKEDITOR.htmlWriter.
|
||||
*
|
||||
* @param {CKEDITOR.htmlParser.basicWriter} writer The writer to which write the HTML.
|
||||
* @param {CKEDITOR.htmlParser.filter} [filter] The filter to be applied to this node.
|
||||
* **Note:** it's unsafe to filter offline (not appended) node.
|
||||
*/
|
||||
writeHtml: function( writer, filter ) {
|
||||
if ( filter )
|
||||
this.filter( filter );
|
||||
|
||||
writer.comment( this.value );
|
||||
}
|
||||
} );
|
||||
568
ckeditor/core/htmlparser/element.js
Normal file
568
ckeditor/core/htmlparser/element.js
Normal file
@@ -0,0 +1,568 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* A lightweight representation of an HTML element.
|
||||
*
|
||||
* @class
|
||||
* @extends CKEDITOR.htmlParser.node
|
||||
* @constructor Creates an element class instance.
|
||||
* @param {String} name The element name.
|
||||
* @param {Object} attributes An object storing all attributes defined for
|
||||
* this element.
|
||||
*/
|
||||
CKEDITOR.htmlParser.element = function( name, attributes ) {
|
||||
/**
|
||||
* The element name.
|
||||
*
|
||||
* @property {String}
|
||||
*/
|
||||
this.name = name;
|
||||
|
||||
/**
|
||||
* Stores the attributes defined for this element.
|
||||
*
|
||||
* @property {Object}
|
||||
*/
|
||||
this.attributes = attributes || {};
|
||||
|
||||
/**
|
||||
* The nodes that are direct children of this element.
|
||||
*/
|
||||
this.children = [];
|
||||
|
||||
// Reveal the real semantic of our internal custom tag name (#6639),
|
||||
// when resolving whether it's block like.
|
||||
var realName = name || '',
|
||||
prefixed = realName.match( /^cke:(.*)/ );
|
||||
prefixed && ( realName = prefixed[ 1 ] );
|
||||
|
||||
var isBlockLike = !!( CKEDITOR.dtd.$nonBodyContent[ realName ] || CKEDITOR.dtd.$block[ realName ] ||
|
||||
CKEDITOR.dtd.$listItem[ realName ] || CKEDITOR.dtd.$tableContent[ realName ] ||
|
||||
CKEDITOR.dtd.$nonEditable[ realName ] || realName == 'br' );
|
||||
|
||||
this.isEmpty = !!CKEDITOR.dtd.$empty[ name ];
|
||||
this.isUnknown = !CKEDITOR.dtd[ name ];
|
||||
|
||||
/** @private */
|
||||
this._ = {
|
||||
isBlockLike: isBlockLike,
|
||||
hasInlineStarted: this.isEmpty || !isBlockLike
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Object presentation of the CSS style declaration text.
|
||||
*
|
||||
* @class
|
||||
* @constructor Creates a `cssStyle` class instance.
|
||||
* @param {CKEDITOR.htmlParser.element/String} elementOrStyleText
|
||||
* An HTML parser element or the inline style text.
|
||||
*/
|
||||
CKEDITOR.htmlParser.cssStyle = function() {
|
||||
var styleText,
|
||||
arg = arguments[ 0 ],
|
||||
rules = {};
|
||||
|
||||
styleText = arg instanceof CKEDITOR.htmlParser.element ? arg.attributes.style : arg;
|
||||
|
||||
// html-encoded quote might be introduced by 'font-family'
|
||||
// from MS-Word which confused the following regexp. e.g.
|
||||
//'font-family: "Lucida, Console"'
|
||||
// TODO reuse CSS methods from tools.
|
||||
( styleText || '' ).replace( /"/g, '"' ).replace( /\s*([^ :;]+)\s*:\s*([^;]+)\s*(?=;|$)/g, function( match, name, value ) {
|
||||
name == 'font-family' && ( value = value.replace( /["']/g, '' ) );
|
||||
rules[ name.toLowerCase() ] = value;
|
||||
} );
|
||||
|
||||
return {
|
||||
|
||||
rules: rules,
|
||||
|
||||
/**
|
||||
* Applies the styles to the specified element or object.
|
||||
*
|
||||
* @param {CKEDITOR.htmlParser.element/CKEDITOR.dom.element/Object} obj
|
||||
*/
|
||||
populate: function( obj ) {
|
||||
var style = this.toString();
|
||||
if ( style )
|
||||
obj instanceof CKEDITOR.dom.element ? obj.setAttribute( 'style', style ) : obj instanceof CKEDITOR.htmlParser.element ? obj.attributes.style = style : obj.style = style;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Serializes CSS style declaration to a string.
|
||||
*
|
||||
* @returns {String}
|
||||
*/
|
||||
toString: function() {
|
||||
var output = [];
|
||||
for ( var i in rules )
|
||||
rules[ i ] && output.push( i, ':', rules[ i ], ';' );
|
||||
return output.join( '' );
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/** @class CKEDITOR.htmlParser.element */
|
||||
( function() {
|
||||
// Used to sort attribute entries in an array, where the first element of
|
||||
// each object is the attribute name.
|
||||
var sortAttribs = function( a, b ) {
|
||||
a = a[ 0 ];
|
||||
b = b[ 0 ];
|
||||
return a < b ? -1 : a > b ? 1 : 0;
|
||||
},
|
||||
fragProto = CKEDITOR.htmlParser.fragment.prototype;
|
||||
|
||||
CKEDITOR.htmlParser.element.prototype = CKEDITOR.tools.extend( new CKEDITOR.htmlParser.node(), {
|
||||
/**
|
||||
* The node type. This is a constant value set to {@link CKEDITOR#NODE_ELEMENT}.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=CKEDITOR.NODE_ELEMENT]
|
||||
*/
|
||||
type: CKEDITOR.NODE_ELEMENT,
|
||||
|
||||
/**
|
||||
* Adds a node to the element children list.
|
||||
*
|
||||
* @method
|
||||
* @param {CKEDITOR.htmlParser.node} node The node to be added.
|
||||
* @param {Number} [index] From where the insertion happens.
|
||||
*/
|
||||
add: fragProto.add,
|
||||
|
||||
/**
|
||||
* Clones this element.
|
||||
*
|
||||
* @returns {CKEDITOR.htmlParser.element} The element clone.
|
||||
*/
|
||||
clone: function() {
|
||||
return new CKEDITOR.htmlParser.element( this.name, this.attributes );
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters this element and its children with the given filter.
|
||||
*
|
||||
* @since 4.1
|
||||
* @param {CKEDITOR.htmlParser.filter} filter
|
||||
* @returns {Boolean} The method returns `false` when this element has
|
||||
* been removed or replaced with another. This information means that
|
||||
* {@link #filterChildren} has to repeat the filter on the current
|
||||
* position in parent's children array.
|
||||
*/
|
||||
filter: function( filter, context ) {
|
||||
var element = this,
|
||||
originalName, name;
|
||||
|
||||
context = element.getFilterContext( context );
|
||||
|
||||
// Do not process elements with data-cke-processor attribute set to off.
|
||||
if ( context.off )
|
||||
return true;
|
||||
|
||||
// Filtering if it's the root node.
|
||||
if ( !element.parent )
|
||||
filter.onRoot( context, element );
|
||||
|
||||
while ( true ) {
|
||||
originalName = element.name;
|
||||
|
||||
if ( !( name = filter.onElementName( context, originalName ) ) ) {
|
||||
this.remove();
|
||||
return false;
|
||||
}
|
||||
|
||||
element.name = name;
|
||||
|
||||
if ( !( element = filter.onElement( context, element ) ) ) {
|
||||
this.remove();
|
||||
return false;
|
||||
}
|
||||
|
||||
// New element has been returned - replace current one
|
||||
// and process it (stop processing this and return false, what
|
||||
// means that element has been removed).
|
||||
if ( element !== this ) {
|
||||
this.replaceWith( element );
|
||||
return false;
|
||||
}
|
||||
|
||||
// If name has been changed - continue loop, so in next iteration
|
||||
// filters for new name will be applied to this element.
|
||||
// If name hasn't been changed - stop.
|
||||
if ( element.name == originalName )
|
||||
break;
|
||||
|
||||
// If element has been replaced with something of a
|
||||
// different type, then make the replacement filter itself.
|
||||
if ( element.type != CKEDITOR.NODE_ELEMENT ) {
|
||||
this.replaceWith( element );
|
||||
return false;
|
||||
}
|
||||
|
||||
// This indicate that the element has been dropped by
|
||||
// filter but not the children.
|
||||
if ( !element.name ) {
|
||||
this.replaceWithChildren();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var attributes = element.attributes,
|
||||
a, value, newAttrName;
|
||||
|
||||
for ( a in attributes ) {
|
||||
newAttrName = a;
|
||||
value = attributes[ a ];
|
||||
|
||||
// Loop until name isn't modified.
|
||||
// A little bit senseless, but IE would do that anyway
|
||||
// because it iterates with for-in loop even over properties
|
||||
// created during its run.
|
||||
while ( true ) {
|
||||
if ( !( newAttrName = filter.onAttributeName( context, a ) ) ) {
|
||||
delete attributes[ a ];
|
||||
break;
|
||||
} else if ( newAttrName != a ) {
|
||||
delete attributes[ a ];
|
||||
a = newAttrName;
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( newAttrName ) {
|
||||
if ( ( value = filter.onAttribute( context, element, newAttrName, value ) ) === false )
|
||||
delete attributes[ newAttrName ];
|
||||
else
|
||||
attributes[ newAttrName ] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !element.isEmpty )
|
||||
this.filterChildren( filter, false, context );
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters this element's children with the given filter.
|
||||
*
|
||||
* Element's children may only be filtered once by one
|
||||
* instance of the filter.
|
||||
*
|
||||
* @method filterChildren
|
||||
* @param {CKEDITOR.htmlParser.filter} filter
|
||||
*/
|
||||
filterChildren: fragProto.filterChildren,
|
||||
|
||||
/**
|
||||
* Writes the element HTML to the CKEDITOR.htmlWriter.
|
||||
*
|
||||
* @param {CKEDITOR.htmlParser.basicWriter} writer The writer to which HTML will be written.
|
||||
* @param {CKEDITOR.htmlParser.filter} [filter] The filter to be applied to this node.
|
||||
* **Note:** It is unsafe to filter an offline (not appended) node.
|
||||
*/
|
||||
writeHtml: function( writer, filter ) {
|
||||
if ( filter )
|
||||
this.filter( filter );
|
||||
|
||||
var name = this.name,
|
||||
attribsArray = [],
|
||||
attributes = this.attributes,
|
||||
attrName,
|
||||
attr, i, l;
|
||||
|
||||
// Open element tag.
|
||||
writer.openTag( name, attributes );
|
||||
|
||||
// Copy all attributes to an array.
|
||||
for ( attrName in attributes )
|
||||
attribsArray.push( [ attrName, attributes[ attrName ] ] );
|
||||
|
||||
// Sort the attributes by name.
|
||||
if ( writer.sortAttributes )
|
||||
attribsArray.sort( sortAttribs );
|
||||
|
||||
// Send the attributes.
|
||||
for ( i = 0, l = attribsArray.length; i < l; i++ ) {
|
||||
attr = attribsArray[ i ];
|
||||
writer.attribute( attr[ 0 ], attr[ 1 ] );
|
||||
}
|
||||
|
||||
// Close the tag.
|
||||
writer.openTagClose( name, this.isEmpty );
|
||||
|
||||
this.writeChildrenHtml( writer );
|
||||
|
||||
// Close the element.
|
||||
if ( !this.isEmpty )
|
||||
writer.closeTag( name );
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends children of this element to the writer.
|
||||
*
|
||||
* @param {CKEDITOR.htmlParser.basicWriter} writer The writer to which HTML will be written.
|
||||
* @param {CKEDITOR.htmlParser.filter} [filter]
|
||||
*/
|
||||
writeChildrenHtml: fragProto.writeChildrenHtml,
|
||||
|
||||
/**
|
||||
* Replaces this element with its children.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
replaceWithChildren: function() {
|
||||
var children = this.children;
|
||||
|
||||
for ( var i = children.length; i; )
|
||||
children[ --i ].insertAfter( this );
|
||||
|
||||
this.remove();
|
||||
},
|
||||
|
||||
/**
|
||||
* Executes a callback on each node (of the given type) in this element.
|
||||
*
|
||||
* // Create a <p> element with foo<b>bar</b>bom as its content.
|
||||
* var elP = CKEDITOR.htmlParser.fragment.fromHtml( 'foo<b>bar</b>bom', 'p' );
|
||||
* elP.forEach( function( node ) {
|
||||
* console.log( node );
|
||||
* } );
|
||||
* // Will log:
|
||||
* // 1. document fragment,
|
||||
* // 2. <p> element,
|
||||
* // 3. "foo" text node,
|
||||
* // 4. <b> element,
|
||||
* // 5. "bar" text node,
|
||||
* // 6. "bom" text node.
|
||||
*
|
||||
* @since 4.1
|
||||
* @param {Function} callback Function to be executed on every node.
|
||||
* **Since 4.3**: If `callback` returned `false`, the descendants of the current node will be ignored.
|
||||
* @param {CKEDITOR.htmlParser.node} callback.node Node passed as an argument.
|
||||
* @param {Number} [type] Whether the specified `callback` will be executed only on nodes of this type.
|
||||
* @param {Boolean} [skipRoot] Do not execute `callback` on this element.
|
||||
*/
|
||||
forEach: fragProto.forEach,
|
||||
|
||||
/**
|
||||
* Gets this element's first child. If `condition` is given, this method returns
|
||||
* the first child which satisfies that condition.
|
||||
*
|
||||
* @since 4.3
|
||||
* @param {String/Object/Function} condition Name of a child, a hash of names, or a validator function.
|
||||
* @returns {CKEDITOR.htmlParser.node}
|
||||
*/
|
||||
getFirst: function( condition ) {
|
||||
if ( !condition )
|
||||
return this.children.length ? this.children[ 0 ] : null;
|
||||
|
||||
if ( typeof condition != 'function' )
|
||||
condition = nameCondition( condition );
|
||||
|
||||
for ( var i = 0, l = this.children.length; i < l; ++i ) {
|
||||
if ( condition( this.children[ i ] ) )
|
||||
return this.children[ i ];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets this element's inner HTML.
|
||||
*
|
||||
* @since 4.3
|
||||
* @returns {String}
|
||||
*/
|
||||
getHtml: function() {
|
||||
var writer = new CKEDITOR.htmlParser.basicWriter();
|
||||
this.writeChildrenHtml( writer );
|
||||
return writer.getHtml();
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets this element's inner HTML.
|
||||
*
|
||||
* @since 4.3
|
||||
* @param {String} html
|
||||
*/
|
||||
setHtml: function( html ) {
|
||||
var children = this.children = CKEDITOR.htmlParser.fragment.fromHtml( html ).children;
|
||||
|
||||
for ( var i = 0, l = children.length; i < l; ++i )
|
||||
children[ i ].parent = this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets this element's outer HTML.
|
||||
*
|
||||
* @since 4.3
|
||||
* @returns {String}
|
||||
*/
|
||||
getOuterHtml: function() {
|
||||
var writer = new CKEDITOR.htmlParser.basicWriter();
|
||||
this.writeHtml( writer );
|
||||
return writer.getHtml();
|
||||
},
|
||||
|
||||
/**
|
||||
* Splits this element at the given index.
|
||||
*
|
||||
* @since 4.3
|
||||
* @param {Number} index Index at which the element will be split — `0` means the beginning,
|
||||
* `1` after the first child node, etc.
|
||||
* @returns {CKEDITOR.htmlParser.element} The new element following this one.
|
||||
*/
|
||||
split: function( index ) {
|
||||
var cloneChildren = this.children.splice( index, this.children.length - index ),
|
||||
clone = this.clone();
|
||||
|
||||
for ( var i = 0; i < cloneChildren.length; ++i )
|
||||
cloneChildren[ i ].parent = clone;
|
||||
|
||||
clone.children = cloneChildren;
|
||||
|
||||
if ( cloneChildren[ 0 ] )
|
||||
cloneChildren[ 0 ].previous = null;
|
||||
|
||||
if ( index > 0 )
|
||||
this.children[ index - 1 ].next = null;
|
||||
|
||||
this.parent.add( clone, this.getIndex() + 1 );
|
||||
|
||||
return clone;
|
||||
},
|
||||
|
||||
/**
|
||||
* Searches through the current node children to find nodes matching the `criteria`.
|
||||
*
|
||||
* @param {String/Function} criteria Tag name or evaluator function.
|
||||
* @param {Boolean} [recursive=false]
|
||||
* @returns {CKEDITOR.htmlParser.node[]}
|
||||
*/
|
||||
find: function( criteria, recursive ) {
|
||||
if ( recursive === undefined ) {
|
||||
recursive = false;
|
||||
}
|
||||
|
||||
var ret = [],
|
||||
i;
|
||||
|
||||
for ( i = 0; i < this.children.length; i++ ) {
|
||||
var curChild = this.children[ i ];
|
||||
|
||||
if ( typeof criteria == 'function' && criteria( curChild ) ) {
|
||||
ret.push( curChild );
|
||||
} else if ( typeof criteria == 'string' && curChild.name === criteria ) {
|
||||
ret.push( curChild );
|
||||
}
|
||||
|
||||
if ( recursive && curChild.find ) {
|
||||
ret = ret.concat( curChild.find( criteria, recursive ) );
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a class name to the list of classes.
|
||||
*
|
||||
* @since 4.4
|
||||
* @param {String} className The class name to be added.
|
||||
*/
|
||||
addClass: function( className ) {
|
||||
if ( this.hasClass( className ) )
|
||||
return;
|
||||
|
||||
var c = this.attributes[ 'class' ] || '';
|
||||
|
||||
this.attributes[ 'class' ] = c + ( c ? ' ' : '' ) + className;
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes a class name from the list of classes.
|
||||
*
|
||||
* @since 4.3
|
||||
* @param {String} className The class name to be removed.
|
||||
*/
|
||||
removeClass: function( className ) {
|
||||
var classes = this.attributes[ 'class' ];
|
||||
|
||||
if ( !classes )
|
||||
return;
|
||||
|
||||
// We can safely assume that className won't break regexp.
|
||||
// http://stackoverflow.com/questions/448981/what-characters-are-valid-in-css-class-names
|
||||
classes = CKEDITOR.tools.trim( classes.replace( new RegExp( '(?:\\s+|^)' + className + '(?:\\s+|$)' ), ' ' ) );
|
||||
|
||||
if ( classes )
|
||||
this.attributes[ 'class' ] = classes;
|
||||
else
|
||||
delete this.attributes[ 'class' ];
|
||||
},
|
||||
|
||||
/**
|
||||
* Checkes whether this element has a class name.
|
||||
*
|
||||
* @since 4.3
|
||||
* @param {String} className The class name to be checked.
|
||||
* @returns {Boolean} Whether this element has a `className`.
|
||||
*/
|
||||
hasClass: function( className ) {
|
||||
var classes = this.attributes[ 'class' ];
|
||||
|
||||
if ( !classes )
|
||||
return false;
|
||||
|
||||
return ( new RegExp( '(?:^|\\s)' + className + '(?=\\s|$)' ) ).test( classes );
|
||||
},
|
||||
|
||||
getFilterContext: function( ctx ) {
|
||||
var changes = [];
|
||||
|
||||
if ( !ctx ) {
|
||||
ctx = {
|
||||
off: false,
|
||||
nonEditable: false,
|
||||
nestedEditable: false
|
||||
};
|
||||
}
|
||||
|
||||
if ( !ctx.off && this.attributes[ 'data-cke-processor' ] == 'off' )
|
||||
changes.push( 'off', true );
|
||||
|
||||
if ( !ctx.nonEditable && this.attributes.contenteditable == 'false' )
|
||||
changes.push( 'nonEditable', true );
|
||||
// A context to be given nestedEditable must be nonEditable first (by inheritance) (#11372, #11698).
|
||||
// Special case: #11504 - filter starts on <body contenteditable=true>,
|
||||
// so ctx.nonEditable has not been yet set to true.
|
||||
else if ( ctx.nonEditable && !ctx.nestedEditable && this.attributes.contenteditable == 'true' )
|
||||
changes.push( 'nestedEditable', true );
|
||||
|
||||
if ( changes.length ) {
|
||||
ctx = CKEDITOR.tools.copy( ctx );
|
||||
for ( var i = 0; i < changes.length; i += 2 )
|
||||
ctx[ changes[ i ] ] = changes[ i + 1 ];
|
||||
}
|
||||
|
||||
return ctx;
|
||||
}
|
||||
}, true );
|
||||
|
||||
function nameCondition( condition ) {
|
||||
return function( el ) {
|
||||
return el.type == CKEDITOR.NODE_ELEMENT &&
|
||||
( typeof condition == 'string' ? el.name == condition : el.name in condition );
|
||||
};
|
||||
}
|
||||
} )();
|
||||
407
ckeditor/core/htmlparser/filter.js
Normal file
407
ckeditor/core/htmlparser/filter.js
Normal file
@@ -0,0 +1,407 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
( function() {
|
||||
/**
|
||||
* Filter is a configurable tool for transforming and filtering {@link CKEDITOR.htmlParser.node nodes}.
|
||||
* It is mainly used during data processing phase which is done not on real DOM nodes,
|
||||
* but on their simplified form represented by {@link CKEDITOR.htmlParser.node} class and its subclasses.
|
||||
*
|
||||
* var filter = new CKEDITOR.htmlParser.filter( {
|
||||
* text: function( value ) {
|
||||
* return '@' + value + '@';
|
||||
* },
|
||||
* elements: {
|
||||
* p: function( element ) {
|
||||
* element.attributes.foo = '1';
|
||||
* }
|
||||
* }
|
||||
* } );
|
||||
*
|
||||
* var fragment = CKEDITOR.htmlParser.fragment.fromHtml( '<p>Foo<b>bar!</b></p>' ),
|
||||
* writer = new CKEDITOR.htmlParser.basicWriter();
|
||||
* filter.applyTo( fragment );
|
||||
* fragment.writeHtml( writer );
|
||||
* writer.getHtml(); // '<p foo="1">@Foo@<b>@bar!@</b></p>'
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
CKEDITOR.htmlParser.filter = CKEDITOR.tools.createClass( {
|
||||
/**
|
||||
* @constructor Creates a filter class instance.
|
||||
* @param {CKEDITOR.htmlParser.filterRulesDefinition} [rules]
|
||||
*/
|
||||
$: function( rules ) {
|
||||
/**
|
||||
* ID of filter instance, which is used to mark elements
|
||||
* to which this filter has been already applied.
|
||||
*
|
||||
* @property {Number} id
|
||||
* @readonly
|
||||
*/
|
||||
this.id = CKEDITOR.tools.getNextNumber();
|
||||
|
||||
/**
|
||||
* Rules for element names.
|
||||
*
|
||||
* @property {CKEDITOR.htmlParser.filterRulesGroup}
|
||||
* @readonly
|
||||
*/
|
||||
this.elementNameRules = new filterRulesGroup();
|
||||
|
||||
/**
|
||||
* Rules for attribute names.
|
||||
*
|
||||
* @property {CKEDITOR.htmlParser.filterRulesGroup}
|
||||
* @readonly
|
||||
*/
|
||||
this.attributeNameRules = new filterRulesGroup();
|
||||
|
||||
/**
|
||||
* Hash of elementName => {@link CKEDITOR.htmlParser.filterRulesGroup rules for elements}.
|
||||
*
|
||||
* @readonly
|
||||
*/
|
||||
this.elementsRules = {};
|
||||
|
||||
/**
|
||||
* Hash of attributeName => {@link CKEDITOR.htmlParser.filterRulesGroup rules for attributes}.
|
||||
*
|
||||
* @readonly
|
||||
*/
|
||||
this.attributesRules = {};
|
||||
|
||||
/**
|
||||
* Rules for text nodes.
|
||||
*
|
||||
* @property {CKEDITOR.htmlParser.filterRulesGroup}
|
||||
* @readonly
|
||||
*/
|
||||
this.textRules = new filterRulesGroup();
|
||||
|
||||
/**
|
||||
* Rules for comment nodes.
|
||||
*
|
||||
* @property {CKEDITOR.htmlParser.filterRulesGroup}
|
||||
* @readonly
|
||||
*/
|
||||
this.commentRules = new filterRulesGroup();
|
||||
|
||||
/**
|
||||
* Rules for a root node.
|
||||
*
|
||||
* @property {CKEDITOR.htmlParser.filterRulesGroup}
|
||||
* @readonly
|
||||
*/
|
||||
this.rootRules = new filterRulesGroup();
|
||||
|
||||
if ( rules )
|
||||
this.addRules( rules, 10 );
|
||||
},
|
||||
|
||||
proto: {
|
||||
/**
|
||||
* Add rules to this filter.
|
||||
*
|
||||
* @param {CKEDITOR.htmlParser.filterRulesDefinition} rules Object containing filter rules.
|
||||
* @param {Object/Number} [options] Object containing rules' options or a priority
|
||||
* (for a backward compatibility with CKEditor versions up to 4.2.x).
|
||||
* @param {Number} [options.priority=10] The priority of a rule.
|
||||
* @param {Boolean} [options.applyToAll=false] Whether to apply rule to non-editable
|
||||
* elements and their descendants too.
|
||||
*/
|
||||
addRules: function( rules, options ) {
|
||||
var priority;
|
||||
|
||||
// Backward compatibility.
|
||||
if ( typeof options == 'number' )
|
||||
priority = options;
|
||||
// New version - try reading from options.
|
||||
else if ( options && ( 'priority' in options ) )
|
||||
priority = options.priority;
|
||||
|
||||
// Defaults.
|
||||
if ( typeof priority != 'number' )
|
||||
priority = 10;
|
||||
if ( typeof options != 'object' )
|
||||
options = {};
|
||||
|
||||
// Add the elementNames.
|
||||
if ( rules.elementNames )
|
||||
this.elementNameRules.addMany( rules.elementNames, priority, options );
|
||||
|
||||
// Add the attributeNames.
|
||||
if ( rules.attributeNames )
|
||||
this.attributeNameRules.addMany( rules.attributeNames, priority, options );
|
||||
|
||||
// Add the elements.
|
||||
if ( rules.elements )
|
||||
addNamedRules( this.elementsRules, rules.elements, priority, options );
|
||||
|
||||
// Add the attributes.
|
||||
if ( rules.attributes )
|
||||
addNamedRules( this.attributesRules, rules.attributes, priority, options );
|
||||
|
||||
// Add the text.
|
||||
if ( rules.text )
|
||||
this.textRules.add( rules.text, priority, options );
|
||||
|
||||
// Add the comment.
|
||||
if ( rules.comment )
|
||||
this.commentRules.add( rules.comment, priority, options );
|
||||
|
||||
// Add root node rules.
|
||||
if ( rules.root )
|
||||
this.rootRules.add( rules.root, priority, options );
|
||||
},
|
||||
|
||||
/**
|
||||
* Apply this filter to given node.
|
||||
*
|
||||
* @param {CKEDITOR.htmlParser.node} node The node to be filtered.
|
||||
*/
|
||||
applyTo: function( node ) {
|
||||
node.filter( this );
|
||||
},
|
||||
|
||||
onElementName: function( context, name ) {
|
||||
return this.elementNameRules.execOnName( context, name );
|
||||
},
|
||||
|
||||
onAttributeName: function( context, name ) {
|
||||
return this.attributeNameRules.execOnName( context, name );
|
||||
},
|
||||
|
||||
onText: function( context, text, node ) {
|
||||
return this.textRules.exec( context, text, node );
|
||||
},
|
||||
|
||||
onComment: function( context, commentText, comment ) {
|
||||
return this.commentRules.exec( context, commentText, comment );
|
||||
},
|
||||
|
||||
onRoot: function( context, element ) {
|
||||
return this.rootRules.exec( context, element );
|
||||
},
|
||||
|
||||
onElement: function( context, element ) {
|
||||
// We must apply filters set to the specific element name as
|
||||
// well as those set to the generic ^/$ name. So, add both to an
|
||||
// array and process them in a small loop.
|
||||
var rulesGroups = [ this.elementsRules[ '^' ], this.elementsRules[ element.name ], this.elementsRules.$ ],
|
||||
rulesGroup, ret;
|
||||
|
||||
for ( var i = 0; i < 3; i++ ) {
|
||||
rulesGroup = rulesGroups[ i ];
|
||||
if ( rulesGroup ) {
|
||||
ret = rulesGroup.exec( context, element, this );
|
||||
|
||||
if ( ret === false )
|
||||
return null;
|
||||
|
||||
if ( ret && ret != element )
|
||||
return this.onNode( context, ret );
|
||||
|
||||
// The non-root element has been dismissed by one of the filters.
|
||||
if ( element.parent && !element.name )
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return element;
|
||||
},
|
||||
|
||||
onNode: function( context, node ) {
|
||||
var type = node.type;
|
||||
|
||||
return type == CKEDITOR.NODE_ELEMENT ? this.onElement( context, node ) :
|
||||
type == CKEDITOR.NODE_TEXT ? new CKEDITOR.htmlParser.text( this.onText( context, node.value ) ) :
|
||||
type == CKEDITOR.NODE_COMMENT ? new CKEDITOR.htmlParser.comment( this.onComment( context, node.value ) ) : null;
|
||||
},
|
||||
|
||||
onAttribute: function( context, element, name, value ) {
|
||||
var rulesGroup = this.attributesRules[ name ];
|
||||
|
||||
if ( rulesGroup )
|
||||
return rulesGroup.exec( context, value, element, this );
|
||||
return value;
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
/**
|
||||
* Class grouping filter rules for one subject (like element or attribute names).
|
||||
*
|
||||
* @class CKEDITOR.htmlParser.filterRulesGroup
|
||||
*/
|
||||
function filterRulesGroup() {
|
||||
/**
|
||||
* Array of objects containing rule, priority and options.
|
||||
*
|
||||
* @property {Object[]}
|
||||
* @readonly
|
||||
*/
|
||||
this.rules = [];
|
||||
}
|
||||
|
||||
CKEDITOR.htmlParser.filterRulesGroup = filterRulesGroup;
|
||||
|
||||
filterRulesGroup.prototype = {
|
||||
/**
|
||||
* Adds specified rule to this group.
|
||||
*
|
||||
* @param {Function/Array} rule Function for function based rule or [ pattern, replacement ] array for
|
||||
* rule applicable to names.
|
||||
* @param {Number} priority
|
||||
* @param options
|
||||
*/
|
||||
add: function( rule, priority, options ) {
|
||||
this.rules.splice( this.findIndex( priority ), 0, {
|
||||
value: rule,
|
||||
priority: priority,
|
||||
options: options
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds specified rules to this group.
|
||||
*
|
||||
* @param {Array} rules Array of rules - see {@link #add}.
|
||||
* @param {Number} priority
|
||||
* @param options
|
||||
*/
|
||||
addMany: function( rules, priority, options ) {
|
||||
var args = [ this.findIndex( priority ), 0 ];
|
||||
|
||||
for ( var i = 0, len = rules.length; i < len; i++ ) {
|
||||
args.push( {
|
||||
value: rules[ i ],
|
||||
priority: priority,
|
||||
options: options
|
||||
} );
|
||||
}
|
||||
|
||||
this.rules.splice.apply( this.rules, args );
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds an index at which rule with given priority should be inserted.
|
||||
*
|
||||
* @param {Number} priority
|
||||
* @returns {Number} Index.
|
||||
*/
|
||||
findIndex: function( priority ) {
|
||||
var rules = this.rules,
|
||||
len = rules.length,
|
||||
i = len - 1;
|
||||
|
||||
// Search from the end, because usually rules will be added with default priority, so
|
||||
// we will be able to stop loop quickly.
|
||||
while ( i >= 0 && priority < rules[ i ].priority )
|
||||
i--;
|
||||
|
||||
return i + 1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Executes this rules group on given value. Applicable only if function based rules were added.
|
||||
*
|
||||
* All arguments passed to this function will be forwarded to rules' functions.
|
||||
*
|
||||
* @param {CKEDITOR.htmlParser.node/CKEDITOR.htmlParser.fragment/String} currentValue The value to be filtered.
|
||||
* @returns {CKEDITOR.htmlParser.node/CKEDITOR.htmlParser.fragment/String} Filtered value.
|
||||
*/
|
||||
exec: function( context, currentValue ) {
|
||||
var isNode = currentValue instanceof CKEDITOR.htmlParser.node || currentValue instanceof CKEDITOR.htmlParser.fragment,
|
||||
// Splice '1' to remove context, which we don't want to pass to filter rules.
|
||||
args = Array.prototype.slice.call( arguments, 1 ),
|
||||
rules = this.rules,
|
||||
len = rules.length,
|
||||
orgType, orgName, ret, i, rule;
|
||||
|
||||
for ( i = 0; i < len; i++ ) {
|
||||
// Backup the node info before filtering.
|
||||
if ( isNode ) {
|
||||
orgType = currentValue.type;
|
||||
orgName = currentValue.name;
|
||||
}
|
||||
|
||||
rule = rules[ i ];
|
||||
if ( isRuleApplicable( context, rule ) ) {
|
||||
ret = rule.value.apply( null, args );
|
||||
|
||||
if ( ret === false )
|
||||
return ret;
|
||||
|
||||
// We're filtering node (element/fragment).
|
||||
// No further filtering if it's not anymore fitable for the subsequent filters.
|
||||
if ( isNode && ret && ( ret.name != orgName || ret.type != orgType ) )
|
||||
return ret;
|
||||
|
||||
// Update currentValue and corresponding argument in args array.
|
||||
// Updated values will be used in next for-loop step.
|
||||
if ( ret != null )
|
||||
args[ 0 ] = currentValue = ret;
|
||||
|
||||
// ret == undefined will continue loop as nothing has happened.
|
||||
}
|
||||
}
|
||||
|
||||
return currentValue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Executes this rules group on name. Applicable only if filter rules for names were added.
|
||||
*
|
||||
* @param {String} currentName The name to be filtered.
|
||||
* @returns {String} Filtered name.
|
||||
*/
|
||||
execOnName: function( context, currentName ) {
|
||||
var i = 0,
|
||||
rules = this.rules,
|
||||
len = rules.length,
|
||||
rule;
|
||||
|
||||
for ( ; currentName && i < len; i++ ) {
|
||||
rule = rules[ i ];
|
||||
if ( isRuleApplicable( context, rule ) )
|
||||
currentName = currentName.replace( rule.value[ 0 ], rule.value[ 1 ] );
|
||||
}
|
||||
|
||||
return currentName;
|
||||
}
|
||||
};
|
||||
|
||||
function addNamedRules( rulesGroups, newRules, priority, options ) {
|
||||
var ruleName, rulesGroup;
|
||||
|
||||
for ( ruleName in newRules ) {
|
||||
rulesGroup = rulesGroups[ ruleName ];
|
||||
|
||||
if ( !rulesGroup )
|
||||
rulesGroup = rulesGroups[ ruleName ] = new filterRulesGroup();
|
||||
|
||||
rulesGroup.add( newRules[ ruleName ], priority, options );
|
||||
}
|
||||
}
|
||||
|
||||
function isRuleApplicable( context, rule ) {
|
||||
if ( context.nonEditable && !rule.options.applyToAll )
|
||||
return false;
|
||||
|
||||
if ( context.nestedEditable && rule.options.excludeNestedEditable )
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} )();
|
||||
|
||||
/**
|
||||
* @class CKEDITOR.htmlParser.filterRulesDefinition
|
||||
* @abstract
|
||||
*/
|
||||
646
ckeditor/core/htmlparser/fragment.js
Normal file
646
ckeditor/core/htmlparser/fragment.js
Normal file
@@ -0,0 +1,646 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* A lightweight representation of an HTML DOM structure.
|
||||
*
|
||||
* @class
|
||||
* @constructor Creates a fragment class instance.
|
||||
*/
|
||||
CKEDITOR.htmlParser.fragment = function() {
|
||||
/**
|
||||
* The nodes contained in the root of this fragment.
|
||||
*
|
||||
* var fragment = CKEDITOR.htmlParser.fragment.fromHtml( '<b>Sample</b> Text' );
|
||||
* alert( fragment.children.length ); // 2
|
||||
*/
|
||||
this.children = [];
|
||||
|
||||
/**
|
||||
* Get the fragment parent. Should always be null.
|
||||
*
|
||||
* @property {Object} [=null]
|
||||
*/
|
||||
this.parent = null;
|
||||
|
||||
/** @private */
|
||||
this._ = {
|
||||
isBlockLike: true,
|
||||
hasInlineStarted: false
|
||||
};
|
||||
};
|
||||
|
||||
( function() {
|
||||
// Block-level elements whose internal structure should be respected during
|
||||
// parser fixing.
|
||||
var nonBreakingBlocks = CKEDITOR.tools.extend( { table: 1, ul: 1, ol: 1, dl: 1 }, CKEDITOR.dtd.table, CKEDITOR.dtd.ul, CKEDITOR.dtd.ol, CKEDITOR.dtd.dl );
|
||||
|
||||
var listBlocks = { ol: 1, ul: 1 };
|
||||
|
||||
// Dtd of the fragment element, basically it accept anything except for intermediate structure, e.g. orphan <li>.
|
||||
var rootDtd = CKEDITOR.tools.extend( {}, { html: 1 }, CKEDITOR.dtd.html, CKEDITOR.dtd.body, CKEDITOR.dtd.head, { style: 1, script: 1 } );
|
||||
|
||||
// Which element to create when encountered not allowed content.
|
||||
var structureFixes = {
|
||||
ul: 'li',
|
||||
ol: 'li',
|
||||
dl: 'dd',
|
||||
table: 'tbody',
|
||||
tbody: 'tr',
|
||||
thead: 'tr',
|
||||
tfoot: 'tr',
|
||||
tr: 'td'
|
||||
};
|
||||
|
||||
function isRemoveEmpty( node ) {
|
||||
// Keep marked element event if it is empty.
|
||||
if ( node.attributes[ 'data-cke-survive' ] )
|
||||
return false;
|
||||
|
||||
// Empty link is to be removed when empty but not anchor. (#7894)
|
||||
return node.name == 'a' && node.attributes.href || CKEDITOR.dtd.$removeEmpty[ node.name ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link CKEDITOR.htmlParser.fragment} from an HTML string.
|
||||
*
|
||||
* var fragment = CKEDITOR.htmlParser.fragment.fromHtml( '<b>Sample</b> Text' );
|
||||
* alert( fragment.children[ 0 ].name ); // 'b'
|
||||
* alert( fragment.children[ 1 ].value ); // ' Text'
|
||||
*
|
||||
* @static
|
||||
* @param {String} fragmentHtml The HTML to be parsed, filling the fragment.
|
||||
* @param {CKEDITOR.htmlParser.element/String} [parent] Optional contextual
|
||||
* element which makes the content been parsed as the content of this element and fix
|
||||
* to match it.
|
||||
* If not provided, then {@link CKEDITOR.htmlParser.fragment} will be used
|
||||
* as the parent and it will be returned.
|
||||
* @param {String/Boolean} [fixingBlock] When `parent` is a block limit element,
|
||||
* and the param is a string value other than `false`, it is to
|
||||
* avoid having block-less content as the direct children of parent by wrapping
|
||||
* the content with a block element of the specified tag, e.g.
|
||||
* when `fixingBlock` specified as `p`, the content `<body><i>foo</i></body>`
|
||||
* will be fixed into `<body><p><i>foo</i></p></body>`.
|
||||
* @returns {CKEDITOR.htmlParser.fragment/CKEDITOR.htmlParser.element} The created fragment or passed `parent`.
|
||||
*/
|
||||
CKEDITOR.htmlParser.fragment.fromHtml = function( fragmentHtml, parent, fixingBlock ) {
|
||||
var parser = new CKEDITOR.htmlParser();
|
||||
|
||||
var root = parent instanceof CKEDITOR.htmlParser.element ? parent : typeof parent == 'string' ? new CKEDITOR.htmlParser.element( parent ) : new CKEDITOR.htmlParser.fragment();
|
||||
|
||||
var pendingInline = [],
|
||||
pendingBRs = [],
|
||||
currentNode = root,
|
||||
// Indicate we're inside a <textarea> element, spaces should be touched differently.
|
||||
inTextarea = root.name == 'textarea',
|
||||
// Indicate we're inside a <pre> element, spaces should be touched differently.
|
||||
inPre = root.name == 'pre';
|
||||
|
||||
function checkPending( newTagName ) {
|
||||
var pendingBRsSent;
|
||||
|
||||
if ( pendingInline.length > 0 ) {
|
||||
for ( var i = 0; i < pendingInline.length; i++ ) {
|
||||
var pendingElement = pendingInline[ i ],
|
||||
pendingName = pendingElement.name,
|
||||
pendingDtd = CKEDITOR.dtd[ pendingName ],
|
||||
currentDtd = currentNode.name && CKEDITOR.dtd[ currentNode.name ];
|
||||
|
||||
if ( ( !currentDtd || currentDtd[ pendingName ] ) && ( !newTagName || !pendingDtd || pendingDtd[ newTagName ] || !CKEDITOR.dtd[ newTagName ] ) ) {
|
||||
if ( !pendingBRsSent ) {
|
||||
sendPendingBRs();
|
||||
pendingBRsSent = 1;
|
||||
}
|
||||
|
||||
// Get a clone for the pending element.
|
||||
pendingElement = pendingElement.clone();
|
||||
|
||||
// Add it to the current node and make it the current,
|
||||
// so the new element will be added inside of it.
|
||||
pendingElement.parent = currentNode;
|
||||
currentNode = pendingElement;
|
||||
|
||||
// Remove the pending element (back the index by one
|
||||
// to properly process the next entry).
|
||||
pendingInline.splice( i, 1 );
|
||||
i--;
|
||||
} else {
|
||||
// Some element of the same type cannot be nested, flat them,
|
||||
// e.g. <a href="#">foo<a href="#">bar</a></a>. (#7894)
|
||||
if ( pendingName == currentNode.name )
|
||||
addElement( currentNode, currentNode.parent, 1 ), i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendPendingBRs() {
|
||||
while ( pendingBRs.length )
|
||||
addElement( pendingBRs.shift(), currentNode );
|
||||
}
|
||||
|
||||
// Rtrim empty spaces on block end boundary. (#3585)
|
||||
function removeTailWhitespace( element ) {
|
||||
if ( element._.isBlockLike && element.name != 'pre' && element.name != 'textarea' ) {
|
||||
|
||||
var length = element.children.length,
|
||||
lastChild = element.children[ length - 1 ],
|
||||
text;
|
||||
if ( lastChild && lastChild.type == CKEDITOR.NODE_TEXT ) {
|
||||
if ( !( text = CKEDITOR.tools.rtrim( lastChild.value ) ) )
|
||||
element.children.length = length - 1;
|
||||
else
|
||||
lastChild.value = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Beside of simply append specified element to target, this function also takes
|
||||
// care of other dirty lifts like forcing block in body, trimming spaces at
|
||||
// the block boundaries etc.
|
||||
//
|
||||
// @param {Element} element The element to be added as the last child of {@link target}.
|
||||
// @param {Element} target The parent element to relieve the new node.
|
||||
// @param {Boolean} [moveCurrent=false] Don't change the "currentNode" global unless
|
||||
// there's a return point node specified on the element, otherwise move current onto {@link target} node.
|
||||
//
|
||||
function addElement( element, target, moveCurrent ) {
|
||||
target = target || currentNode || root;
|
||||
|
||||
// Current element might be mangled by fix body below,
|
||||
// save it for restore later.
|
||||
var savedCurrent = currentNode;
|
||||
|
||||
// Ignore any element that has already been added.
|
||||
if ( element.previous === undefined ) {
|
||||
if ( checkAutoParagraphing( target, element ) ) {
|
||||
// Create a <p> in the fragment.
|
||||
currentNode = target;
|
||||
parser.onTagOpen( fixingBlock, {} );
|
||||
|
||||
// The new target now is the <p>.
|
||||
element.returnPoint = target = currentNode;
|
||||
}
|
||||
|
||||
removeTailWhitespace( element );
|
||||
|
||||
// Avoid adding empty inline.
|
||||
if ( !( isRemoveEmpty( element ) && !element.children.length ) )
|
||||
target.add( element );
|
||||
|
||||
if ( element.name == 'pre' )
|
||||
inPre = false;
|
||||
|
||||
if ( element.name == 'textarea' )
|
||||
inTextarea = false;
|
||||
}
|
||||
|
||||
if ( element.returnPoint ) {
|
||||
currentNode = element.returnPoint;
|
||||
delete element.returnPoint;
|
||||
} else {
|
||||
currentNode = moveCurrent ? target : savedCurrent;
|
||||
}
|
||||
}
|
||||
|
||||
// Auto paragraphing should happen when inline content enters the root element.
|
||||
function checkAutoParagraphing( parent, node ) {
|
||||
|
||||
// Check for parent that can contain block.
|
||||
if ( ( parent == root || parent.name == 'body' ) && fixingBlock &&
|
||||
( !parent.name || CKEDITOR.dtd[ parent.name ][ fixingBlock ] ) ) {
|
||||
var name, realName;
|
||||
|
||||
if ( node.attributes && ( realName = node.attributes[ 'data-cke-real-element-type' ] ) )
|
||||
name = realName;
|
||||
else
|
||||
name = node.name;
|
||||
|
||||
// Text node, inline elements are subjected, except for <script>/<style>.
|
||||
return name && name in CKEDITOR.dtd.$inline &&
|
||||
!( name in CKEDITOR.dtd.head ) &&
|
||||
!node.isOrphan ||
|
||||
node.type == CKEDITOR.NODE_TEXT;
|
||||
}
|
||||
}
|
||||
|
||||
// Judge whether two element tag names are likely the siblings from the same
|
||||
// structural element.
|
||||
function possiblySibling( tag1, tag2 ) {
|
||||
|
||||
if ( tag1 in CKEDITOR.dtd.$listItem || tag1 in CKEDITOR.dtd.$tableContent )
|
||||
return tag1 == tag2 || tag1 == 'dt' && tag2 == 'dd' || tag1 == 'dd' && tag2 == 'dt';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
parser.onTagOpen = function( tagName, attributes, selfClosing, optionalClose ) {
|
||||
var element = new CKEDITOR.htmlParser.element( tagName, attributes );
|
||||
|
||||
// "isEmpty" will be always "false" for unknown elements, so we
|
||||
// must force it if the parser has identified it as a selfClosing tag.
|
||||
if ( element.isUnknown && selfClosing )
|
||||
element.isEmpty = true;
|
||||
|
||||
// Check for optional closed elements, including browser quirks and manually opened blocks.
|
||||
element.isOptionalClose = optionalClose;
|
||||
|
||||
// This is a tag to be removed if empty, so do not add it immediately.
|
||||
if ( isRemoveEmpty( element ) ) {
|
||||
pendingInline.push( element );
|
||||
return;
|
||||
} else if ( tagName == 'pre' )
|
||||
inPre = true;
|
||||
else if ( tagName == 'br' && inPre ) {
|
||||
currentNode.add( new CKEDITOR.htmlParser.text( '\n' ) );
|
||||
return;
|
||||
} else if ( tagName == 'textarea' ) {
|
||||
inTextarea = true;
|
||||
}
|
||||
|
||||
if ( tagName == 'br' ) {
|
||||
pendingBRs.push( element );
|
||||
return;
|
||||
}
|
||||
|
||||
while ( 1 ) {
|
||||
var currentName = currentNode.name;
|
||||
|
||||
var currentDtd = currentName ? ( CKEDITOR.dtd[ currentName ] || ( currentNode._.isBlockLike ? CKEDITOR.dtd.div : CKEDITOR.dtd.span ) ) : rootDtd;
|
||||
|
||||
// If the element cannot be child of the current element.
|
||||
if ( !element.isUnknown && !currentNode.isUnknown && !currentDtd[ tagName ] ) {
|
||||
// Current node doesn't have a close tag, time for a close
|
||||
// as this element isn't fit in. (#7497)
|
||||
if ( currentNode.isOptionalClose )
|
||||
parser.onTagClose( currentName );
|
||||
// Fixing malformed nested lists by moving it into a previous list item. (#3828)
|
||||
else if ( tagName in listBlocks && currentName in listBlocks ) {
|
||||
var children = currentNode.children,
|
||||
lastChild = children[ children.length - 1 ];
|
||||
|
||||
// Establish the list item if it's not existed.
|
||||
if ( !( lastChild && lastChild.name == 'li' ) )
|
||||
addElement( ( lastChild = new CKEDITOR.htmlParser.element( 'li' ) ), currentNode );
|
||||
|
||||
!element.returnPoint && ( element.returnPoint = currentNode );
|
||||
currentNode = lastChild;
|
||||
}
|
||||
// Establish new list root for orphan list items, but NOT to create
|
||||
// new list for the following ones, fix them instead. (#6975)
|
||||
// <dl><dt>foo<dd>bar</dl>
|
||||
// <ul><li>foo<li>bar</ul>
|
||||
else if ( tagName in CKEDITOR.dtd.$listItem &&
|
||||
!possiblySibling( tagName, currentName ) ) {
|
||||
parser.onTagOpen( tagName == 'li' ? 'ul' : 'dl', {}, 0, 1 );
|
||||
}
|
||||
// We're inside a structural block like table and list, AND the incoming element
|
||||
// is not of the same type (e.g. <td>td1<td>td2</td>), we simply add this new one before it,
|
||||
// and most importantly, return back to here once this element is added,
|
||||
// e.g. <table><tr><td>td1</td><p>p1</p><td>td2</td></tr></table>
|
||||
else if ( currentName in nonBreakingBlocks &&
|
||||
!possiblySibling( tagName, currentName ) ) {
|
||||
!element.returnPoint && ( element.returnPoint = currentNode );
|
||||
currentNode = currentNode.parent;
|
||||
} else {
|
||||
// The current element is an inline element, which
|
||||
// need to be continued even after the close, so put
|
||||
// it in the pending list.
|
||||
if ( currentName in CKEDITOR.dtd.$inline )
|
||||
pendingInline.unshift( currentNode );
|
||||
|
||||
// The most common case where we just need to close the
|
||||
// current one and append the new one to the parent.
|
||||
if ( currentNode.parent )
|
||||
addElement( currentNode, currentNode.parent, 1 );
|
||||
// We've tried our best to fix the embarrassment here, while
|
||||
// this element still doesn't find it's parent, mark it as
|
||||
// orphan and show our tolerance to it.
|
||||
else {
|
||||
element.isOrphan = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
checkPending( tagName );
|
||||
sendPendingBRs();
|
||||
|
||||
element.parent = currentNode;
|
||||
|
||||
if ( element.isEmpty )
|
||||
addElement( element );
|
||||
else
|
||||
currentNode = element;
|
||||
};
|
||||
|
||||
parser.onTagClose = function( tagName ) {
|
||||
// Check if there is any pending tag to be closed.
|
||||
for ( var i = pendingInline.length - 1; i >= 0; i-- ) {
|
||||
// If found, just remove it from the list.
|
||||
if ( tagName == pendingInline[ i ].name ) {
|
||||
pendingInline.splice( i, 1 );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var pendingAdd = [],
|
||||
newPendingInline = [],
|
||||
candidate = currentNode;
|
||||
|
||||
while ( candidate != root && candidate.name != tagName ) {
|
||||
// If this is an inline element, add it to the pending list, if we're
|
||||
// really closing one of the parents element later, they will continue
|
||||
// after it.
|
||||
if ( !candidate._.isBlockLike )
|
||||
newPendingInline.unshift( candidate );
|
||||
|
||||
// This node should be added to it's parent at this point. But,
|
||||
// it should happen only if the closing tag is really closing
|
||||
// one of the nodes. So, for now, we just cache it.
|
||||
pendingAdd.push( candidate );
|
||||
|
||||
// Make sure return point is properly restored.
|
||||
candidate = candidate.returnPoint || candidate.parent;
|
||||
}
|
||||
|
||||
if ( candidate != root ) {
|
||||
// Add all elements that have been found in the above loop.
|
||||
for ( i = 0; i < pendingAdd.length; i++ ) {
|
||||
var node = pendingAdd[ i ];
|
||||
addElement( node, node.parent );
|
||||
}
|
||||
|
||||
currentNode = candidate;
|
||||
|
||||
if ( candidate._.isBlockLike )
|
||||
sendPendingBRs();
|
||||
|
||||
addElement( candidate, candidate.parent );
|
||||
|
||||
// The parent should start receiving new nodes now, except if
|
||||
// addElement changed the currentNode.
|
||||
if ( candidate == currentNode )
|
||||
currentNode = currentNode.parent;
|
||||
|
||||
pendingInline = pendingInline.concat( newPendingInline );
|
||||
}
|
||||
|
||||
if ( tagName == 'body' )
|
||||
fixingBlock = false;
|
||||
};
|
||||
|
||||
parser.onText = function( text ) {
|
||||
// Trim empty spaces at beginning of text contents except <pre> and <textarea>.
|
||||
if ( ( !currentNode._.hasInlineStarted || pendingBRs.length ) && !inPre && !inTextarea ) {
|
||||
text = CKEDITOR.tools.ltrim( text );
|
||||
|
||||
if ( text.length === 0 )
|
||||
return;
|
||||
}
|
||||
|
||||
var currentName = currentNode.name,
|
||||
currentDtd = currentName ? ( CKEDITOR.dtd[ currentName ] || ( currentNode._.isBlockLike ? CKEDITOR.dtd.div : CKEDITOR.dtd.span ) ) : rootDtd;
|
||||
|
||||
// Fix orphan text in list/table. (#8540) (#8870)
|
||||
if ( !inTextarea && !currentDtd[ '#' ] && currentName in nonBreakingBlocks ) {
|
||||
parser.onTagOpen( structureFixes[ currentName ] || '' );
|
||||
parser.onText( text );
|
||||
return;
|
||||
}
|
||||
|
||||
sendPendingBRs();
|
||||
checkPending();
|
||||
|
||||
// Shrinking consequential spaces into one single for all elements
|
||||
// text contents.
|
||||
if ( !inPre && !inTextarea )
|
||||
text = text.replace( /[\t\r\n ]{2,}|[\t\r\n]/g, ' ' );
|
||||
|
||||
text = new CKEDITOR.htmlParser.text( text );
|
||||
|
||||
|
||||
if ( checkAutoParagraphing( currentNode, text ) )
|
||||
this.onTagOpen( fixingBlock, {}, 0, 1 );
|
||||
|
||||
currentNode.add( text );
|
||||
};
|
||||
|
||||
parser.onCDATA = function( cdata ) {
|
||||
currentNode.add( new CKEDITOR.htmlParser.cdata( cdata ) );
|
||||
};
|
||||
|
||||
parser.onComment = function( comment ) {
|
||||
sendPendingBRs();
|
||||
checkPending();
|
||||
currentNode.add( new CKEDITOR.htmlParser.comment( comment ) );
|
||||
};
|
||||
|
||||
// Parse it.
|
||||
parser.parse( fragmentHtml );
|
||||
|
||||
sendPendingBRs();
|
||||
|
||||
// Close all pending nodes, make sure return point is properly restored.
|
||||
while ( currentNode != root )
|
||||
addElement( currentNode, currentNode.parent, 1 );
|
||||
|
||||
removeTailWhitespace( root );
|
||||
|
||||
return root;
|
||||
};
|
||||
|
||||
CKEDITOR.htmlParser.fragment.prototype = {
|
||||
|
||||
/**
|
||||
* The node type. This is a constant value set to {@link CKEDITOR#NODE_DOCUMENT_FRAGMENT}.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=CKEDITOR.NODE_DOCUMENT_FRAGMENT]
|
||||
*/
|
||||
type: CKEDITOR.NODE_DOCUMENT_FRAGMENT,
|
||||
|
||||
/**
|
||||
* Adds a node to this fragment.
|
||||
*
|
||||
* @param {CKEDITOR.htmlParser.node} node The node to be added.
|
||||
* @param {Number} [index] From where the insertion happens.
|
||||
*/
|
||||
add: function( node, index ) {
|
||||
isNaN( index ) && ( index = this.children.length );
|
||||
|
||||
var previous = index > 0 ? this.children[ index - 1 ] : null;
|
||||
if ( previous ) {
|
||||
// If the block to be appended is following text, trim spaces at
|
||||
// the right of it.
|
||||
if ( node._.isBlockLike && previous.type == CKEDITOR.NODE_TEXT ) {
|
||||
previous.value = CKEDITOR.tools.rtrim( previous.value );
|
||||
|
||||
// If we have completely cleared the previous node.
|
||||
if ( previous.value.length === 0 ) {
|
||||
// Remove it from the list and add the node again.
|
||||
this.children.pop();
|
||||
this.add( node );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
previous.next = node;
|
||||
}
|
||||
|
||||
node.previous = previous;
|
||||
node.parent = this;
|
||||
|
||||
this.children.splice( index, 0, node );
|
||||
|
||||
if ( !this._.hasInlineStarted )
|
||||
this._.hasInlineStarted = node.type == CKEDITOR.NODE_TEXT || ( node.type == CKEDITOR.NODE_ELEMENT && !node._.isBlockLike );
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter this fragment's content with given filter.
|
||||
*
|
||||
* @since 4.1
|
||||
* @param {CKEDITOR.htmlParser.filter} filter
|
||||
*/
|
||||
filter: function( filter, context ) {
|
||||
context = this.getFilterContext( context );
|
||||
|
||||
// Apply the root filter.
|
||||
filter.onRoot( context, this );
|
||||
|
||||
this.filterChildren( filter, false, context );
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter this fragment's children with given filter.
|
||||
*
|
||||
* Element's children may only be filtered once by one
|
||||
* instance of filter.
|
||||
*
|
||||
* @since 4.1
|
||||
* @param {CKEDITOR.htmlParser.filter} filter
|
||||
* @param {Boolean} [filterRoot] Whether to apply the "root" filter rule specified in the `filter`.
|
||||
*/
|
||||
filterChildren: function( filter, filterRoot, context ) {
|
||||
// If this element's children were already filtered
|
||||
// by current filter, don't filter them 2nd time.
|
||||
// This situation may occur when filtering bottom-up
|
||||
// (filterChildren() called manually in element's filter),
|
||||
// or in unpredictable edge cases when filter
|
||||
// is manipulating DOM structure.
|
||||
if ( this.childrenFilteredBy == filter.id )
|
||||
return;
|
||||
|
||||
context = this.getFilterContext( context );
|
||||
|
||||
// Filtering root if enforced.
|
||||
if ( filterRoot && !this.parent )
|
||||
filter.onRoot( context, this );
|
||||
|
||||
this.childrenFilteredBy = filter.id;
|
||||
|
||||
// Don't cache anything, children array may be modified by filter rule.
|
||||
for ( var i = 0; i < this.children.length; i++ ) {
|
||||
// Stay in place if filter returned false, what means
|
||||
// that node has been removed.
|
||||
if ( this.children[ i ].filter( filter, context ) === false )
|
||||
i--;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes the fragment HTML to a {@link CKEDITOR.htmlParser.basicWriter}.
|
||||
*
|
||||
* var writer = new CKEDITOR.htmlWriter();
|
||||
* var fragment = CKEDITOR.htmlParser.fragment.fromHtml( '<P><B>Example' );
|
||||
* fragment.writeHtml( writer );
|
||||
* alert( writer.getHtml() ); // '<p><b>Example</b></p>'
|
||||
*
|
||||
* @param {CKEDITOR.htmlParser.basicWriter} writer The writer to which write the HTML.
|
||||
* @param {CKEDITOR.htmlParser.filter} [filter] The filter to use when writing the HTML.
|
||||
*/
|
||||
writeHtml: function( writer, filter ) {
|
||||
if ( filter )
|
||||
this.filter( filter );
|
||||
|
||||
this.writeChildrenHtml( writer );
|
||||
},
|
||||
|
||||
/**
|
||||
* Write and filtering the child nodes of this fragment.
|
||||
*
|
||||
* @param {CKEDITOR.htmlParser.basicWriter} writer The writer to which write the HTML.
|
||||
* @param {CKEDITOR.htmlParser.filter} [filter] The filter to use when writing the HTML.
|
||||
* @param {Boolean} [filterRoot] Whether to apply the "root" filter rule specified in the `filter`.
|
||||
*/
|
||||
writeChildrenHtml: function( writer, filter, filterRoot ) {
|
||||
var context = this.getFilterContext();
|
||||
|
||||
// Filtering root if enforced.
|
||||
if ( filterRoot && !this.parent && filter )
|
||||
filter.onRoot( context, this );
|
||||
|
||||
if ( filter )
|
||||
this.filterChildren( filter, false, context );
|
||||
|
||||
for ( var i = 0, children = this.children, l = children.length; i < l; i++ )
|
||||
children[ i ].writeHtml( writer );
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute callback on each node (of given type) in this document fragment.
|
||||
*
|
||||
* var fragment = CKEDITOR.htmlParser.fragment.fromHtml( '<p>foo<b>bar</b>bom</p>' );
|
||||
* fragment.forEach( function( node ) {
|
||||
* console.log( node );
|
||||
* } );
|
||||
* // Will log:
|
||||
* // 1. document fragment,
|
||||
* // 2. <p> element,
|
||||
* // 3. "foo" text node,
|
||||
* // 4. <b> element,
|
||||
* // 5. "bar" text node,
|
||||
* // 6. "bom" text node.
|
||||
*
|
||||
* @since 4.1
|
||||
* @param {Function} callback Function to be executed on every node.
|
||||
* **Since 4.3** if `callback` returned `false` descendants of current node will be ignored.
|
||||
* @param {CKEDITOR.htmlParser.node} callback.node Node passed as argument.
|
||||
* @param {Number} [type] If specified `callback` will be executed only on nodes of this type.
|
||||
* @param {Boolean} [skipRoot] Don't execute `callback` on this fragment.
|
||||
*/
|
||||
forEach: function( callback, type, skipRoot ) {
|
||||
if ( !skipRoot && ( !type || this.type == type ) )
|
||||
var ret = callback( this );
|
||||
|
||||
// Do not filter children if callback returned false.
|
||||
if ( ret === false )
|
||||
return;
|
||||
|
||||
var children = this.children,
|
||||
node,
|
||||
i = 0;
|
||||
|
||||
// We do not cache the size, because the list of nodes may be changed by the callback.
|
||||
for ( ; i < children.length; i++ ) {
|
||||
node = children[ i ];
|
||||
if ( node.type == CKEDITOR.NODE_ELEMENT )
|
||||
node.forEach( callback, type );
|
||||
else if ( !type || node.type == type )
|
||||
callback( node );
|
||||
}
|
||||
},
|
||||
|
||||
getFilterContext: function( context ) {
|
||||
return context || {};
|
||||
}
|
||||
};
|
||||
} )();
|
||||
156
ckeditor/core/htmlparser/node.js
Normal file
156
ckeditor/core/htmlparser/node.js
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
( function() {
|
||||
/**
|
||||
* A lightweight representation of HTML node.
|
||||
*
|
||||
* @since 4.1
|
||||
* @class
|
||||
* @constructor Creates a node class instance.
|
||||
*/
|
||||
CKEDITOR.htmlParser.node = function() {};
|
||||
|
||||
CKEDITOR.htmlParser.node.prototype = {
|
||||
/**
|
||||
* Remove this node from a tree.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
remove: function() {
|
||||
var children = this.parent.children,
|
||||
index = CKEDITOR.tools.indexOf( children, this ),
|
||||
previous = this.previous,
|
||||
next = this.next;
|
||||
|
||||
previous && ( previous.next = next );
|
||||
next && ( next.previous = previous );
|
||||
children.splice( index, 1 );
|
||||
this.parent = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Replace this node with given one.
|
||||
*
|
||||
* @since 4.1
|
||||
* @param {CKEDITOR.htmlParser.node} node The node that will replace this one.
|
||||
*/
|
||||
replaceWith: function( node ) {
|
||||
var children = this.parent.children,
|
||||
index = CKEDITOR.tools.indexOf( children, this ),
|
||||
previous = node.previous = this.previous,
|
||||
next = node.next = this.next;
|
||||
|
||||
previous && ( previous.next = node );
|
||||
next && ( next.previous = node );
|
||||
|
||||
children[ index ] = node;
|
||||
|
||||
node.parent = this.parent;
|
||||
this.parent = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Insert this node after given one.
|
||||
*
|
||||
* @since 4.1
|
||||
* @param {CKEDITOR.htmlParser.node} node The node that will precede this element.
|
||||
*/
|
||||
insertAfter: function( node ) {
|
||||
var children = node.parent.children,
|
||||
index = CKEDITOR.tools.indexOf( children, node ),
|
||||
next = node.next;
|
||||
|
||||
children.splice( index + 1, 0, this );
|
||||
|
||||
this.next = node.next;
|
||||
this.previous = node;
|
||||
node.next = this;
|
||||
next && ( next.previous = this );
|
||||
|
||||
this.parent = node.parent;
|
||||
},
|
||||
|
||||
/**
|
||||
* Insert this node before given one.
|
||||
*
|
||||
* @since 4.1
|
||||
* @param {CKEDITOR.htmlParser.node} node The node that will follow this element.
|
||||
*/
|
||||
insertBefore: function( node ) {
|
||||
var children = node.parent.children,
|
||||
index = CKEDITOR.tools.indexOf( children, node );
|
||||
|
||||
children.splice( index, 0, this );
|
||||
|
||||
this.next = node;
|
||||
this.previous = node.previous;
|
||||
node.previous && ( node.previous.next = this );
|
||||
node.previous = this;
|
||||
|
||||
this.parent = node.parent;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the closest ancestor element of this element which satisfies given condition
|
||||
*
|
||||
* @since 4.3
|
||||
* @param {String/Object/Function} condition Name of an ancestor, hash of names or validator function.
|
||||
* @returns {CKEDITOR.htmlParser.element} The closest ancestor which satisfies given condition or `null`.
|
||||
*/
|
||||
getAscendant: function( condition ) {
|
||||
var checkFn =
|
||||
typeof condition == 'function' ?
|
||||
condition :
|
||||
typeof condition == 'string' ?
|
||||
function( el ) {
|
||||
return el.name == condition;
|
||||
} :
|
||||
function( el ) {
|
||||
return el.name in condition;
|
||||
};
|
||||
|
||||
var parent = this.parent;
|
||||
|
||||
// Parent has to be an element - don't check doc fragment.
|
||||
while ( parent && parent.type == CKEDITOR.NODE_ELEMENT ) {
|
||||
if ( checkFn( parent ) )
|
||||
return parent;
|
||||
parent = parent.parent;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Wraps this element with given `wrapper`.
|
||||
*
|
||||
* @since 4.3
|
||||
* @param {CKEDITOR.htmlParser.element} wrapper The element which will be this element's new parent.
|
||||
* @returns {CKEDITOR.htmlParser.element} Wrapper.
|
||||
*/
|
||||
wrapWith: function( wrapper ) {
|
||||
this.replaceWith( wrapper );
|
||||
wrapper.add( this );
|
||||
return wrapper;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets this node's index in its parent's children array.
|
||||
*
|
||||
* @since 4.3
|
||||
* @returns {Number}
|
||||
*/
|
||||
getIndex: function() {
|
||||
return CKEDITOR.tools.indexOf( this.parent.children, this );
|
||||
},
|
||||
|
||||
getFilterContext: function( context ) {
|
||||
return context || {};
|
||||
}
|
||||
};
|
||||
} )();
|
||||
70
ckeditor/core/htmlparser/text.js
Normal file
70
ckeditor/core/htmlparser/text.js
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
( function() {
|
||||
/**
|
||||
* A lightweight representation of HTML text.
|
||||
*
|
||||
* @class
|
||||
* @extends CKEDITOR.htmlParser.node
|
||||
* @constructor Creates a text class instance.
|
||||
* @param {String} value The text node value.
|
||||
*/
|
||||
CKEDITOR.htmlParser.text = function( value ) {
|
||||
/**
|
||||
* The text value.
|
||||
*
|
||||
* @property {String}
|
||||
*/
|
||||
this.value = value;
|
||||
|
||||
/** @private */
|
||||
this._ = {
|
||||
isBlockLike: false
|
||||
};
|
||||
};
|
||||
|
||||
CKEDITOR.htmlParser.text.prototype = CKEDITOR.tools.extend( new CKEDITOR.htmlParser.node(), {
|
||||
/**
|
||||
* The node type. This is a constant value set to {@link CKEDITOR#NODE_TEXT}.
|
||||
*
|
||||
* @readonly
|
||||
* @property {Number} [=CKEDITOR.NODE_TEXT]
|
||||
*/
|
||||
type: CKEDITOR.NODE_TEXT,
|
||||
|
||||
/**
|
||||
* Filter this text node with given filter.
|
||||
*
|
||||
* @since 4.1
|
||||
* @param {CKEDITOR.htmlParser.filter} filter
|
||||
* @returns {Boolean} Method returns `false` when this text node has
|
||||
* been removed. This is an information for {@link CKEDITOR.htmlParser.element#filterChildren}
|
||||
* that it has to repeat filter on current position in parent's children array.
|
||||
*/
|
||||
filter: function( filter, context ) {
|
||||
if ( !( this.value = filter.onText( context, this.value, this ) ) ) {
|
||||
this.remove();
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes the HTML representation of this text to a {CKEDITOR.htmlParser.basicWriter}.
|
||||
*
|
||||
* @param {CKEDITOR.htmlParser.basicWriter} writer The writer to which write the HTML.
|
||||
* @param {CKEDITOR.htmlParser.filter} [filter] The filter to be applied to this node.
|
||||
* **Note:** it's unsafe to filter offline (not appended) node.
|
||||
*/
|
||||
writeHtml: function( writer, filter ) {
|
||||
if ( filter )
|
||||
this.filter( filter );
|
||||
|
||||
writer.text( this.value );
|
||||
}
|
||||
} );
|
||||
} )();
|
||||
Reference in New Issue
Block a user