Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge branch 'master' into t/629b
Browse files Browse the repository at this point in the history
  • Loading branch information
Reinmar committed Nov 15, 2016
2 parents a3690d5 + 7642972 commit b1e0e1f
Show file tree
Hide file tree
Showing 14 changed files with 407 additions and 59 deletions.
94 changes: 50 additions & 44 deletions src/dev-utils/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,18 @@ import Range from '../view/range.js';
import Position from '../view/position.js';
import AttributeElement from '../view/attributeelement.js';
import ContainerElement from '../view/containerelement.js';
import EmptyElement from '../view/emptyelement.js';
import ViewText from '../view/text.js';

const ELEMENT_RANGE_START_TOKEN = '[';
const ELEMENT_RANGE_END_TOKEN = ']';
const TEXT_RANGE_START_TOKEN = '{';
const TEXT_RANGE_END_TOKEN = '}';
const allowedTypes = {
'container': ContainerElement,
'attribute': AttributeElement,
'empty': EmptyElement,
};

/**
* Writes the contents of the {@link engine.view.Document Document} to an HTML-like string.
Expand All @@ -38,7 +44,7 @@ const TEXT_RANGE_END_TOKEN = '}';
* @param {Boolean} [options.rootName='main'] Name of the root from which data should be stringified. If not provided
* default `main` name will be used.
* @param {Boolean} [options.showType=false] When set to `true` type of elements will be printed (`<container:p>`
* instead of `<p>` and `<attribute:b>` instead of `<b>`).
* instead of `<p>`, `<attribute:b>` instead of `<b>` and `<empty:img>` instead of `<img>`).
* @param {Boolean} [options.showPriority=false] When set to `true` AttributeElement's priority will be printed
* (`<span view-priority="12">`, `<b view-priority="10">`).
* @returns {String} The stringified data.
Expand Down Expand Up @@ -162,13 +168,15 @@ setData._parse = parse;
*
* Additional options object can be provided.
* If `options.showType` is set to `true`, element's types will be
* presented for {@link engine.view.AttributeElement AttributeElements} and {@link engine.view.ContainerElement
* ContainerElements}:
* presented for {@link engine.view.AttributeElement AttributeElements}, {@link engine.view.ContainerElement
* ContainerElements} and {@link engine.view.EmptyElement EmptyElements}:
*
* const attribute = new AttributeElement( 'b' );
* const container = new ContainerElement( 'p' );
* const empty = new EmptyElement( 'img' );
* getData( attribute, null, { showType: true } ); // '<attribute:b></attribute:b>'
* getData( container, null, { showType: true } ); // '<container:p></container:p>'
* getData( empty, null, { showType: true } ); // '<empty:img></empty:img>'
*
* If `options.showPriority` is set to `true`, priority will be displayed for all
* {@link engine.view.AttributeElement AttributeElements}.
Expand All @@ -185,7 +193,7 @@ setData._parse = parse;
* containing one range collapsed at this position.
* @param {Object} [options] Object with additional options.
* @param {Boolean} [options.showType=false] When set to `true` type of elements will be printed (`<container:p>`
* instead of `<p>` and `<attribute:b>` instead of `<b>`).
* instead of `<p>`, `<attribute:b>` instead of `<b>` and `<empty:img>` instead of `<img>`).
* @param {Boolean} [options.showPriority=false] When set to `true` AttributeElement's priority will be printed
* (`<span view-priority="12">`, `<b view-priority="10">`).
* @param {Boolean} [options.ignoreRoot=false] When set to `true` root's element opening and closing will not be printed.
Expand Down Expand Up @@ -288,7 +296,7 @@ export function parse( data, options = {} ) {
sameSelectionCharacters: options.sameSelectionCharacters
} );
const processor = new XmlDataProcessor( {
namespaces: [ 'attribute', 'container' ]
namespaces: Object.keys( allowedTypes )
} );

// Convert data to view.
Expand Down Expand Up @@ -560,8 +568,8 @@ class ViewStringify {
* @param root
* @param {engine.view.Selection} [selection=null] Selection which ranges should be also converted to string.
* @param {Object} [options] Options object.
* @param {Boolean} [options.showType=false] When set to `true` type of elements will be printed ( `<container:p>`
* instead of `<p>` and `<attribute:b>` instead of `<b>`.
* @param {Boolean} [options.showType=false] When set to `true` type of elements will be printed (`<container:p>`
* instead of `<p>`, `<attribute:b>` instead of `<b>` and `<empty:img>` instead of `<img>`).
* @param {Boolean} [options.showPriority=false] When set to `true` AttributeElement's priority will be printed.
* @param {Boolean} [options.ignoreRoot=false] When set to `true` root's element opening and closing tag will not
* be outputted.
Expand Down Expand Up @@ -719,9 +727,9 @@ class ViewStringify {

/**
* Converts passed {@link engine.view.Element Element} to opening tag.
* Depending on current configuration opening tag can be simple (`<a>`), contain type prefix (`<container:p>` or
* `<attribute:a>`), contain priority information ( `<attribute:a view-priority="20">` ). Element's attributes also
* will be included (`<a href="http://ckeditor.com" name="foobar">`).
* Depending on current configuration opening tag can be simple (`<a>`), contain type prefix (`<container:p>`,
* `<attribute:a>` or `<empty:img>`), contain priority information ( `<attribute:a view-priority="20">` ).
* Element's attributes also will be included (`<a href="http://ckeditor.com" name="foobar">`).
*
* @private
* @param {engine.view.Element} element
Expand All @@ -740,8 +748,8 @@ class ViewStringify {

/**
* Converts passed {@link engine.view.Element Element} to closing tag.
* Depending on current configuration closing tag can be simple (`</a>`) or contain type prefix (`</container:p>` or
* `</attribute:a>`).
* Depending on current configuration closing tag can be simple (`</a>`) or contain type prefix (`</container:p>`,
* `</attribute:a>` or `</empty:img>`).
*
* @private
* @param {engine.view.Element} element
Expand All @@ -756,22 +764,21 @@ class ViewStringify {

/**
* Converts passed {@link engine.view.Element Element's} type to its string representation
* Returns 'attribute' for {@link engine.view.AttributeElement AttributeElements} and
* 'container' for {@link engine.view.ContainerElement ContainerElements}. Returns empty string when current
* configuration is preventing showing elements' types.
* Returns 'attribute' for {@link engine.view.AttributeElement AttributeElements},
* 'container' for {@link engine.view.ContainerElement ContainerElements} and 'empty' for
* {@link engine.view.EmptyElement EmptyElements}. Returns empty string when current configuration is preventing
* showing elements' types.
*
* @private
* @param {engine.view.Element} element
* @returns {String}
*/
_stringifyElementType( element ) {
if ( this.showType ) {
if ( element instanceof AttributeElement ) {
return 'attribute';
}

if ( element instanceof ContainerElement ) {
return 'container';
for ( let type in allowedTypes ) {
if ( element instanceof allowedTypes[ type ] ) {
return type;
}
}
}

Expand Down Expand Up @@ -816,13 +823,14 @@ class ViewStringify {
}
}

// Converts {@link engine.view.Element Elements} to {@link engine.view.AttributeElement AttributeElements} and
// {@link engine.view.ContainerElement ContainerElements}. It converts whole tree starting from the `rootNode`.
// Conversion is based on element names. See `_convertElement` method for more details.
// Converts {@link engine.view.Element Elements} to {@link engine.view.AttributeElement AttributeElements},
// {@link engine.view.ContainerElement ContainerElements} or {@link engine.view.EmptyElement EmptyElements}.
// It converts whole tree starting from the `rootNode`. Conversion is based on element names.
// See `_convertElement` method for more details.
//
// @param {engine.view.Element|engine.view.DocumentFragment|engine.view.Text} rootNode Root node to convert.
// @returns {engine.view.Element|engine.view.DocumentFragment|engine.view.Text|engine.view.AttributeElement|
// engine.view.ContainerElement} Root node of converted elements.
// engine.view.ContainerElement|engine.view.EmptyElement} Root node of converted elements.
function _convertViewElements( rootNode ) {
const isFragment = rootNode instanceof ViewDocumentFragment;

Expand All @@ -832,6 +840,10 @@ function _convertViewElements( rootNode ) {

// Convert all child nodes.
for ( let child of rootNode.getChildren() ) {
if ( convertedElement instanceof EmptyElement ) {
throw new Error( `Parse error - cannot parse inside EmptyElement.` );
}

convertedElement.appendChildren( _convertViewElements( child ) );
}

Expand All @@ -841,32 +853,29 @@ function _convertViewElements( rootNode ) {
return rootNode;
}

// Converts {@link engine.view.Element Element} to {@link engine.view.AttributeElement AttributeElement} or
// {@link engine.view.ContainerElement ContainerElement}.
// Converts {@link engine.view.Element Element} to {@link engine.view.AttributeElement AttributeElement},
// {@link engine.view.ContainerElement ContainerElement} or {@link engine.view.EmptyElement EmptyElement}.
// If element's name is in format `attribute:b` with `view-priority="11"` attribute it will be converted to
// {@link engine.view.AttributeElement AttributeElement} with priority 11.
// If element's name is in format `container:p` - it will be converted to
// {@link engine.view.ContainerElement ContainerElement}.
// If element's name is in format `empty:img` - it will be converted to
// {@link engine.view.EmptyElement EmptyElement}.
// If element's name will not contain any additional information - {@link engine.view.Element view Element} will be
// returned.
//
// @param {engine.view.Element} viewElement View element to convert.
// @returns {engine.view.Element|engine.view.AttributeElement|engine.view.ContainerElement} Tree view
// element converted according to it's name.
function _convertElement( viewElement ) {
let newElement;
const info = _convertElementNameAndPriority( viewElement );
const ElementConstructor = allowedTypes[ info.type ];
const newElement = ElementConstructor ? new ElementConstructor( info.name ) : new ViewElement( info.name );

if ( info.type == 'attribute' ) {
newElement = new AttributeElement( info.name );

if ( newElement instanceof AttributeElement ) {
if ( info.priority !== null ) {
newElement.priority = info.priority;
}
} else if ( info.type == 'container' ) {
newElement = new ContainerElement( info.name );
} else {
newElement = new ViewElement( info.name );
}

// Move attributes.
Expand All @@ -878,14 +887,15 @@ function _convertElement( viewElement ) {
}

// Converts `view-priority` attribute and {@link engine.view.Element#name Element's name} information needed for creating
// {@link engine.view.AttributeElement AttributeElement} or {@link engine.view.ContainerElement ContainerElement} instance.
// {@link engine.view.AttributeElement AttributeElement}, {@link engine.view.ContainerElement ContainerElement} or
// {@link engine.view.EmptyElement EmptyElement} instance.
// Name can be provided in two formats: as a simple element's name (`div`), or as a type and name (`container:div`,
// `attribute:span`);
// `attribute:span`, `empty:img`);
//
// @param {engine.view.Element} element Element which name should be converted.
// @returns {Object} info Object with parsed information.
// @returns {String} info.name Parsed name of the element.
// @returns {String|null} info.type Parsed type of the element, can be `attribute` or `container`.
// @returns {String|null} info.type Parsed type of the element, can be `attribute`, `container` or `empty`.
// returns {Number|null} info.priority Parsed priority of the element.
function _convertElementNameAndPriority( viewElement ) {
const parts = viewElement.name.split( ':' );
Expand Down Expand Up @@ -914,16 +924,12 @@ function _convertElementNameAndPriority( viewElement ) {
throw new Error( `Parse error - cannot parse element's name: ${ viewElement.name }.` );
}

// Checks if element's type is allowed. Returns `attribute`, `container` or `null`.
// Checks if element's type is allowed. Returns `attribute`, `container`, `empty` or `null`.
//
// @param {String} type
// @returns {String|null}
function _convertType( type ) {
if ( type == 'container' || type == 'attribute' ) {
return type;
}

return null;
return allowedTypes[ type ] ? type : null;
}

// Checks if given priority is allowed. Returns null if priority cannot be converted.
Expand Down
6 changes: 3 additions & 3 deletions src/view/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import isIterable from '../../utils/isiterable.js';
import isPlainObject from '../../utils/lib/lodash/isPlainObject.js';

/**
* Tree view element.
* View element.
*
* Editing engine does not define fixed HTML DTD. This is why the type of the {@link engine.view.Element} need to
* be defined by the feature developer. Creating an element you should use {@link engine.view.ContainerElement}
* class or {@link engine.view.AttributeElement}.
* class, {@link engine.view.AttributeElement} class or {@link engine.view.EmptyElement} class.
*
* Note that for view elements which are not created from model, like elements from mutations, paste or
* {@link engine.controller.DataController#set data.set} it is not possible to define the type of the element, so
Expand All @@ -25,7 +25,7 @@ import isPlainObject from '../../utils/lib/lodash/isPlainObject.js';
*/
export default class Element extends Node {
/**
* Creates a tree view element.
* Creates a view element.
*
* Attributes can be passed in various formats:
*
Expand Down
82 changes: 82 additions & 0 deletions src/view/emptyelement.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md.
*/

import Element from './element.js';
import CKEditorError from '../../utils/ckeditorerror.js';

/**
* EmptyElement class. It is used to represent elements that cannot contain any child nodes.
*/
export default class EmptyElement extends Element {
/**
* Creates new instance of EmptyElement.
*
* Throws {@link utils.CKEditorError CKEditorError} `view-emptyelement-cannot-add` when third parameter is passed,
* to inform that usage of EmptyElement is incorrect (adding child nodes to EmptyElement is forbidden).
*
* @param {String} name Node name.
* @param {Object|Iterable} [attributes] Collection of attributes.
*/
constructor( name, attributes ) {
super( name, attributes );

if ( arguments.length > 2 ) {
throwCannotAdd();
}
}

/**
* Clones provided element. Overrides {@link engine.view.Element#clone} method, as it's forbidden to pass child
* nodes to EmptyElement's constructor.
*
* @returns {envine.view.EmptyElement} Clone of this element.
*/
clone() {
const cloned = new this.constructor( this.name, this._attrs );

// Classes and styles are cloned separately - this solution is faster than adding them back to attributes and
// parse once again in constructor.
cloned._classes = new Set( this._classes );
cloned._styles = new Map( this._styles );

return cloned;
}

/**
* Overrides {@link engine.view.Element#appendChildren} method.
* Throws {@link utils.CKEditorError CKEditorError} `view-emptyelement-cannot-add` to prevent adding any child nodes
* to EmptyElement.
*/
appendChildren() {
throwCannotAdd();
}

/**
* Overrides {@link engine.view.Element#insertChildren} method.
* Throws {@link utils.CKEditorError CKEditorError} `view-emptyelement-cannot-add` to prevent adding any child nodes
* to EmptyElement.
*/
insertChildren() {
throwCannotAdd();
}

/**
* Returns `null` because block filler is not needed.
*
* @returns {null}
*/
getFillerOffset() {
return null;
}
}

function throwCannotAdd() {
/**
* Cannot add children to {@link engine.view.EmptyElement}.
*
* @error view-emptyelement-cannot-add
*/
throw new CKEditorError( 'view-emptyelement-cannot-add: Cannot add child nodes to EmptyElement instance.' );
}
Loading

0 comments on commit b1e0e1f

Please sign in to comment.