|
|
@@ -9,7 +9,7 @@
|
|
|
|
|
|
import Element from '@ckeditor/ckeditor5-engine/src/view/element';
|
|
|
import Matcher from '@ckeditor/ckeditor5-engine/src/view/matcher';
|
|
|
-import Position from '@ckeditor/ckeditor5-engine/src/view/position';
|
|
|
+import Range from '@ckeditor/ckeditor5-engine/src/view/range';
|
|
|
import TreeWalker from '@ckeditor/ckeditor5-engine/src/view/treewalker';
|
|
|
import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter';
|
|
|
|
|
|
@@ -21,50 +21,62 @@ import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter';
|
|
|
* <p class=MsoListParagraphCxSpFirst style='mso-list:l1 level1 lfo1'>...</p> // Paragraph based list.
|
|
|
* <h1 style='mso-list:l0 level1 lfo1'>...</h1> // Heading 1 based list.
|
|
|
*
|
|
|
- * @param {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment} bodyView The view
|
|
|
- * structure which to transform.
|
|
|
- * @returns {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment} The view
|
|
|
- * structure instance with list-like elements transformed into semantic lists.
|
|
|
+ * @param {module:engine/view/documentfragment~DocumentFragment} documentFragment The view structure which to transform.
|
|
|
+ * @param {String} stylesString Styles from which list-like elements styling will be extracted.
|
|
|
*/
|
|
|
-export function transformParagraphsToLists( bodyView, stylesString ) {
|
|
|
- const firstChild = bodyView.getChild( 0 );
|
|
|
-
|
|
|
- if ( firstChild ) {
|
|
|
- const listNodes = findAllListNodes( Position.createBefore( firstChild ) );
|
|
|
- createLists( listNodes, stylesString );
|
|
|
+export function transformListItemLikeElementsIntoLists( documentFragment, stylesString ) {
|
|
|
+ if ( !documentFragment.childCount ) {
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- return bodyView;
|
|
|
+ const listLikeItems = findAllListItemLikeElements( documentFragment );
|
|
|
+
|
|
|
+ if ( listLikeItems.length ) {
|
|
|
+ const writer = new UpcastWriter();
|
|
|
+
|
|
|
+ let currentList = null;
|
|
|
+
|
|
|
+ for ( let i = 0; i < listLikeItems.length; i++ ) {
|
|
|
+ if ( !currentList || listLikeItems[ i - 1 ].id !== listLikeItems[ i ].id ) {
|
|
|
+ const listStyle = findListStyle( listLikeItems[ i ], stylesString );
|
|
|
+ currentList = insertEmptyList( listStyle, listLikeItems[ i ].element, writer );
|
|
|
+ }
|
|
|
+
|
|
|
+ const listItem = transformElementIntoListItem( listLikeItems[ i ].element, writer );
|
|
|
+
|
|
|
+ writer.appendChild( listItem, currentList );
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-// Finds all list-like nodes starting from a given position.
|
|
|
+// Finds all list-like elements in a given document fragment.
|
|
|
//
|
|
|
-// @param {module:engine/src/view/position~Position} startPosition Position from which to start looking.
|
|
|
-// @returns {Array.<Object>} Array of found list items. Each item is an object containing:
|
|
|
+// @param {module:engine/view/documentfragment~DocumentFragment} documentFragment Document fragment
|
|
|
+// in which to look for list-like nodes.
|
|
|
+// @returns {Array.<Object>} Array of found list-like items. Each item is an object containing:
|
|
|
//
|
|
|
// * {module:engine/src/view/element~Element} element List-like element.
|
|
|
// * {Number} id List item id parsed from `mso-list` style (see `getListItemData()` function).
|
|
|
// * {Number} order List item creation order parsed from `mso-list` style (see `getListItemData()` function).
|
|
|
// * {Number} indent List item indentation level parsed from `mso-list` style (see `getListItemData()` function).
|
|
|
-function findAllListNodes( startPosition ) {
|
|
|
- const treeWalker = new TreeWalker( { startPosition, ignoreElementEnd: true } );
|
|
|
+function findAllListItemLikeElements( documentFragment ) {
|
|
|
+ const treeWalker = new TreeWalker( { boundaries: Range.createIn( documentFragment ), ignoreElementEnd: true } );
|
|
|
|
|
|
// Matcher for finding list-like elements.
|
|
|
- const listMatcher = new Matcher( {
|
|
|
+ const listItemLikeElementsMatcher = new Matcher( {
|
|
|
name: /^p|h\d+$/,
|
|
|
styles: {
|
|
|
'mso-list': /.*/
|
|
|
}
|
|
|
} );
|
|
|
|
|
|
- // Find all list nodes.
|
|
|
- const listNodes = [];
|
|
|
+ const listLikeItems = [];
|
|
|
|
|
|
for ( const value of treeWalker ) {
|
|
|
- if ( value.type === 'elementStart' && listMatcher.match( value.item ) ) {
|
|
|
+ if ( value.type === 'elementStart' && listItemLikeElementsMatcher.match( value.item ) ) {
|
|
|
const itemData = getListItemData( value.item );
|
|
|
|
|
|
- listNodes.push( {
|
|
|
+ listLikeItems.push( {
|
|
|
element: value.item,
|
|
|
id: itemData.id,
|
|
|
order: itemData.order,
|
|
|
@@ -73,41 +85,79 @@ function findAllListNodes( startPosition ) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- return listNodes;
|
|
|
+ return listLikeItems;
|
|
|
}
|
|
|
|
|
|
-// Transforms given list-like nodes into semantic lists. As the function operates on a provided
|
|
|
-// {module:engine/src/view/element~Element elements}, it will modify the view structure to which those list elements belongs.
|
|
|
+// Checks list item style based on a provided CSS.
|
|
|
//
|
|
|
-// @param {Array.<Object>} listItems Array containing list items data. Usually it is the output of `findAllListNodes()` function.
|
|
|
-// @param {String} styles CSS styles which may contain additional data about lists format.
|
|
|
-function createLists( listItems, styles ) {
|
|
|
- if ( listItems.length ) {
|
|
|
- const writer = new UpcastWriter();
|
|
|
+// List item style is extracted from CSS stylesheet. Each list with its specific style attribute
|
|
|
+// value (`mso-list:l1 level1 lfo1`) has its dedicated properties in a CSS stylesheet defined with a selector like:
|
|
|
+//
|
|
|
+// @list l1:level1 { ... }
|
|
|
+//
|
|
|
+// It contains `mso-level-number-format` property which defines list numbering/bullet style. If this property
|
|
|
+// is not defined it means default `decimal` numbering.
|
|
|
+//
|
|
|
+// Here CSS string representation is used as `mso-level-number-format` property is an invalid CSS property
|
|
|
+// and will be removed during CSS parsing.
|
|
|
+//
|
|
|
+// @param {Object} listLikeItem List-like item for which list style will be searched for. Usually
|
|
|
+// a result of `findAllListItemLikeElements()` function.
|
|
|
+// @param {String} stylesString CSS stylesheet.
|
|
|
+// @returns {Object} result
|
|
|
+// @returns {String} result.type List type, could be `ul` or `ol`.
|
|
|
+// @returns {String} result.style List style, for example: `decimal`, `lower-roman`, etc. It is extracted
|
|
|
+// directly from Word stylesheet without further processing and may be not compatible
|
|
|
+// with CSS `list-style-type` property accepted values.
|
|
|
+function findListStyle( listLikeItem, stylesString ) {
|
|
|
+ const listStyleRegexp = new RegExp( `@list l${ listLikeItem.id }:level${ listLikeItem.indent }\\s*({[^}]*)`, 'gi' );
|
|
|
+ const listStyleTypeRegex = /mso-level-number-format:([^;]*);/gi;
|
|
|
|
|
|
- let currentList = null;
|
|
|
- let previousListItem = null;
|
|
|
+ const listStyleMatch = listStyleRegexp.exec( stylesString );
|
|
|
|
|
|
- for ( const listItem of listItems ) {
|
|
|
- const listNode = listItem.element;
|
|
|
+ let listStyleType = 'decimal'; // Decimal is default one.
|
|
|
+ if ( listStyleMatch && listStyleMatch[ 1 ] ) {
|
|
|
+ const listStyleTypeMatch = listStyleTypeRegex.exec( listStyleMatch[ 1 ] );
|
|
|
|
|
|
- if ( !previousListItem || previousListItem.id !== listItem.id ) {
|
|
|
- const listStyle = findListType( listItem, styles );
|
|
|
- currentList = new Element( listStyle.type );
|
|
|
- writer.insertChild( listNode.parent.getChildIndex( listNode ), currentList, listNode.parent );
|
|
|
- }
|
|
|
+ if ( listStyleTypeMatch && listStyleTypeMatch[ 1 ] ) {
|
|
|
+ listStyleType = listStyleTypeMatch[ 1 ].trim();
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- removeBulletElement( listNode, writer );
|
|
|
+ return {
|
|
|
+ type: listStyleType !== 'bullet' && listStyleType !== 'image' ? 'ol' : 'ul',
|
|
|
+ style: listStyleType
|
|
|
+ };
|
|
|
+}
|
|
|
|
|
|
- writer.appendChild( listNode, currentList );
|
|
|
- writer.rename( 'li', listNode );
|
|
|
+// Creates empty list of a given type and inserts it after a specified element.
|
|
|
+//
|
|
|
+// @param {Object} listStyle List style object which determines the type of newly created list.
|
|
|
+// Usually a result of `findListStyle()` function.
|
|
|
+// @param {module:engine/view/element~Element} element Element before which list is inserted.
|
|
|
+// @param {module:engine/view/upcastwriter~UpcastWriter} writer
|
|
|
+// @returns {module:engine/view/element~Element} Newly created list element.
|
|
|
+function insertEmptyList( listStyle, element, writer ) {
|
|
|
+ const list = new Element( listStyle.type );
|
|
|
+ const position = element.parent.getChildIndex( element );
|
|
|
|
|
|
- previousListItem = listItem;
|
|
|
- }
|
|
|
- }
|
|
|
+ writer.insertChild( position, list, element.parent );
|
|
|
+
|
|
|
+ return list;
|
|
|
+}
|
|
|
+
|
|
|
+// Transforms given element into a semantic list item. As the function operates on a provided
|
|
|
+// {module:engine/src/view/element~Element element} it will modify the view structure to which this element belongs.
|
|
|
+//
|
|
|
+// @param {module:engine/view/element~Element} element Element which will be transformed into list item.
|
|
|
+// @param {module:engine/view/upcastwriter~UpcastWriter} writer
|
|
|
+function transformElementIntoListItem( element, writer ) {
|
|
|
+ removeBulletElement( element, writer );
|
|
|
+
|
|
|
+ return writer.rename( 'li', element );
|
|
|
}
|
|
|
|
|
|
-// Extracts list information from Word specific list style like:
|
|
|
+// Extracts list item information from Word specific list-like element style:
|
|
|
//
|
|
|
// `style="mso-list:l1 level1 lfo1"`
|
|
|
//
|
|
|
@@ -117,11 +167,11 @@ function createLists( listItems, styles ) {
|
|
|
// * `level1` is a list item indentation level,
|
|
|
// * `lfo1` is a list insertion order in a document.
|
|
|
//
|
|
|
-// @param {module:engine/view/element~Element} element List-like element from which data is extracted.
|
|
|
+// @param {module:engine/view/element~Element} element Element from which style data is extracted.
|
|
|
// @returns {Object} result
|
|
|
-// @returns {Number} result.id List id.
|
|
|
-// @returns {Number} result.order List creation order.
|
|
|
-// @returns {Number} result.indent List indentation level.
|
|
|
+// @returns {Number} result.id Parent list id.
|
|
|
+// @returns {Number} result.order List item creation order.
|
|
|
+// @returns {Number} result.indent List item indentation level.
|
|
|
function getListItemData( element ) {
|
|
|
const data = {};
|
|
|
const listStyle = element.getStyle( 'mso-list' );
|
|
|
@@ -135,50 +185,11 @@ function getListItemData( element ) {
|
|
|
return data;
|
|
|
}
|
|
|
|
|
|
-// Checks list item style based on a provided CSS.
|
|
|
-//
|
|
|
-// List item style is extracted from CSS stylesheet. Each list with its specific style attribute value (`mso-list:l1 level1 lfo1`)
|
|
|
-// has its dedicated properties in a CSS stylesheet defined with a selector like:
|
|
|
+// Removes span with a numbering/bullet from a given element.
|
|
|
//
|
|
|
-// @list l1:level1 { ... }
|
|
|
-//
|
|
|
-// It contains `mso-level-number-format` property which defines list numbering/bullet style. If this property
|
|
|
-// is not defined it means default `decimal` numbering.
|
|
|
-//
|
|
|
-// Here CSS string representation is used as `mso-level-number-format` is invalid CSS property which gets removed during parsing.
|
|
|
-//
|
|
|
-// @param {Object} listItem List item for which list style will be searched for.
|
|
|
-// @param {String} styles CSS stylesheet.
|
|
|
-// @returns {Object} result
|
|
|
-// @returns {String} result.type Type of the list, could be `ul` or `ol`.
|
|
|
-// @returns {String} result.style List style like `decimal`, `lower-roman`, etc. It is passed directly from Word stylesheet
|
|
|
-// so may be not compatible with CSS `list-style-type` accepted values.
|
|
|
-function findListType( listItem, styles ) {
|
|
|
- const listStyleRegexp = new RegExp( `@list l${ listItem.id }:level${ listItem.indent }\\s*({[^}]*)`, 'gi' );
|
|
|
- const listStyleTypeRegex = /mso-level-number-format:([^;]*);/gi;
|
|
|
-
|
|
|
- const listStyleMatch = listStyleRegexp.exec( styles );
|
|
|
-
|
|
|
- let listStyleType = 'decimal'; // Decimal is default one.
|
|
|
- if ( listStyleMatch && listStyleMatch[ 1 ] ) {
|
|
|
- const listStyleTypeMatch = listStyleTypeRegex.exec( listStyleMatch[ 1 ] );
|
|
|
-
|
|
|
- if ( listStyleTypeMatch && listStyleTypeMatch[ 1 ] ) {
|
|
|
- listStyleType = listStyleTypeMatch[ 1 ].trim();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return {
|
|
|
- type: listStyleType !== 'bullet' && listStyleType !== 'image' ? 'ol' : 'ul',
|
|
|
- style: listStyleType
|
|
|
- };
|
|
|
-}
|
|
|
-
|
|
|
-// Removes span with a numbering/bullet from the given list element.
|
|
|
-//
|
|
|
-// @param {module:engine/view/element~Element} listElement
|
|
|
+// @param {module:engine/view/element~Element} element
|
|
|
// @param {module:engine/view/upcastwriter~UpcastWriter} writer
|
|
|
-function removeBulletElement( listElement, writer ) {
|
|
|
+function removeBulletElement( element, writer ) {
|
|
|
// Matcher for finding `span` elements holding lists numbering/bullets.
|
|
|
const bulletMatcher = new Matcher( {
|
|
|
name: 'span',
|
|
|
@@ -187,7 +198,10 @@ function removeBulletElement( listElement, writer ) {
|
|
|
}
|
|
|
} );
|
|
|
|
|
|
- const treeWalker = new TreeWalker( { startPosition: Position.createBefore( listElement.getChild( 0 ) ), ignoreElementEnd: true } );
|
|
|
+ const treeWalker = new TreeWalker( {
|
|
|
+ boundaries: Range.createIn( element ),
|
|
|
+ ignoreElementEnd: true
|
|
|
+ } );
|
|
|
|
|
|
for ( const value of treeWalker ) {
|
|
|
if ( value.type === 'elementStart' && bulletMatcher.match( value.item ) ) {
|