瀏覽代碼

Basic implementation of setData().

Piotrek Koszuliński 9 年之前
父節點
當前提交
415b045ba8
共有 2 個文件被更改,包括 193 次插入2 次删除
  1. 70 1
      packages/ckeditor5-engine/tests/_utils-tests/model.js
  2. 123 1
      packages/ckeditor5-engine/tests/_utils/model.js

+ 70 - 1
packages/ckeditor5-engine/tests/_utils-tests/model.js

@@ -5,7 +5,7 @@
 
 'use strict';
 
-import { getData } from '/tests/engine/_utils/model.js';
+import { getData, setData } from '/tests/engine/_utils/model.js';
 import Document from '/ckeditor5/engine/treemodel/document.js';
 import Element from '/ckeditor5/engine/treemodel/element.js';
 import Text from '/ckeditor5/engine/treemodel/text.js';
@@ -189,4 +189,73 @@ describe( 'model test utils', () => {
 			} );
 		} );
 	} );
+
+	describe( 'setData', () => {
+		test( 'creates elements', {
+			data: '<a></a><b><c></c></b>'
+		} );
+
+		test( 'creates text nodes', {
+			data: 'foo<a>bar</a>bom'
+		} );
+
+		test( 'sets elements attributes', {
+			data: '<a foo=1 bar=true car="x y"><b x="y"></b></a>',
+			output: '<a bar=true car="x y" foo=1><b x="y"></b></a>',
+			check() {
+				expect( root.getChild( 0 ).getAttribute( 'car' ) ).to.equal( 'x y' );
+			}
+		} );
+
+		test( 'sets complex attributes', {
+			data: '<a foo={"a":1,"b":"c"}></a>',
+			check() {
+				expect( root.getChild( 0 ).getAttribute( 'a' ) ).to.equal( 1 );
+				expect( root.getChild( 0 ).getAttribute( 'b' ) ).to.equal( 'c' );
+			}
+		} );
+
+		test( 'sets text attributes', {
+			data: '<$text bold=true italic=true>foo</$text><$text bold=true>bar</$text>bom',
+			check() {
+				expect( root.getChildCount() ).to.equal( 3 );
+				expect( root.getChild( 0 ) ).to.have.property( 'text', 'foo' );
+				expect( root.getChild( 0 ).getAttribute( 'italic' ) ).to.equal( true );
+			}
+		} );
+
+		it( 'throws when unexpected closing tag', () => {
+			expect( () => {
+				setData( document, 'main', '<a><b></a></b>' );
+			} ).to.throw();
+		} );
+
+		it( 'throws when unexpected attribute', () => {
+			expect( () => {
+				setData( document, 'main', '<a ?></a>' );
+			} ).to.throw();
+		} );
+
+		it( 'throws when incorrect tag', () => {
+			expect( () => {
+				setData( document, 'main', '<a' );
+			} ).to.throw();
+		} );
+
+		it( 'throws when missing closing tag', () => {
+			expect( () => {
+				setData( document, 'main', '<a><b></b>' );
+			} ).to.throw();
+		} );
+
+		function test( title, options ) {
+			it( title, () => {
+				let output = options.output || options.data;
+
+				setData( document, 'main', options.data, options.setDataOptions );
+
+				expect( getData( document, 'main', options.getDataOptions ) ).to.equal( output );
+			} );
+		}
+	} );
 } );

+ 123 - 1
packages/ckeditor5-engine/tests/_utils/model.js

@@ -8,6 +8,8 @@
 import TreeWalker from '/ckeditor5/engine/treemodel/treewalker.js';
 import Range from '/ckeditor5/engine/treemodel/range.js';
 import Position from '/ckeditor5/engine/treemodel/position.js';
+import Text from '/ckeditor5/engine/treemodel/text.js';
+import Element from '/ckeditor5/engine/treemodel/element.js';
 
 /**
  * Writes the contents of the document to an HTML-like string.
@@ -44,7 +46,34 @@ export function getData( document, rootName, options ) {
 	return ret;
 }
 
-// -- Private stuff -----------------------------------------------------------
+export function setData( document, rootName, data ) {
+	let appendTo = document.getRoot( rootName );
+	const path = [];
+
+	for ( let token of tokenize( data ) ) {
+		if ( token.type == 'text' ) {
+			appendTo.appendChildren( new Text( token.text, token.attributes ) );
+		} else if ( token.type == 'openingTag' ) {
+			let el = new Element( token.name, token.attributes );
+			appendTo.appendChildren( el );
+
+			appendTo = el;
+			path.push( token.name );
+		} else {
+			if ( path.pop() != token.name ) {
+				throw new Error( 'Parse error - unexpected closing tag.' );
+			}
+
+			appendTo = appendTo.parent;
+		}
+	}
+
+	if ( path.length ) {
+		throw new Error( 'Parse error - missing closing tags: ' + path.join( ', ' ) + '.' );
+	}
+}
+
+// -- getData helpers ---------------------------------------------------------
 
 function writeItem( walkerValue, selection, options ) {
 	const type = walkerValue.type;
@@ -134,3 +163,96 @@ function writeSelection( currentPosition, selection ) {
 
 	return ret;
 }
+
+// -- setData helpers ---------------------------------------------------------
+
+const patterns = {
+	textTag: /^<\$text ([^>]+)>([\s\S]+?)<\/\$text>/,
+	tag: /^<([^>]+)>/,
+	text: /^[^<]+/
+};
+const handlers = {
+	textTag( match ) {
+		return {
+			type: 'text',
+			attributes: parseAttributes( match[ 1 ] ),
+			text: match[ 2 ]
+		};
+	},
+
+	tag( match ) {
+		const tagContents = match[ 1 ].split( /\s+/ );
+		const tagName = tagContents.shift();
+		const attrs = tagContents.join( ' ' );
+
+		if ( tagName[ 0 ] == '/' ) {
+			return {
+				type: 'closingTag',
+				name: tagName.slice( 1 )
+			};
+		}
+
+		return {
+			type: 'openingTag',
+			name: tagName,
+			attributes: parseAttributes( attrs )
+		};
+	},
+
+	text( match ) {
+		return {
+			type: 'text',
+			text: match[ 0 ]
+		};
+	}
+};
+
+function *tokenize( data ) {
+	while ( data ) {
+		const consumed = consumeNextToken( data );
+
+		data = consumed.data;
+		yield consumed.token;
+	}
+}
+
+function consumeNextToken( data ) {
+	let match;
+
+	for ( let patternName in patterns ) {
+		match = data.match( patterns[ patternName ] );
+
+		if ( match ) {
+			data = data.slice( match[ 0 ].length );
+
+			return {
+				token: handlers[ patternName ]( match ),
+				data
+			};
+		}
+	}
+
+	throw new Error( 'Parse error - unpexpected token: ' + data + '.' );
+}
+
+function parseAttributes( attrsString  ) {
+	if ( !attrsString  ) {
+		return {};
+	}
+
+	const pattern = /(\w+)=("[^"]+"|[^\s]+)\s*/;
+	const attrs = {};
+
+	while ( attrsString ) {
+		let match = attrsString.match( pattern );
+
+		if ( !match ) {
+			throw new Error( 'Parse error - unexpected token: ' + attrsString + '.' );
+		}
+
+		attrs[ match[ 1 ] ] = JSON.parse( match[ 2 ] );
+		attrsString = attrsString.slice( match[ 0 ].length );
+	}
+
+	return attrs;
+}