Added optional "unrestricted" argument to isStructuralOffset

Using this argument will only return true if the offset is a place you can add any element to (hence the unrestricted part of it). This is good for testing if a paragraph could potentially be inserted there.

Change-Id: I6cc91da437c52493de03eb687b28966198270fea
This commit is contained in:
Trevor Parscal 2012-05-23 14:56:33 -07:00
parent fe1482e0c4
commit 541d786ced
2 changed files with 91 additions and 42 deletions

View file

@ -207,19 +207,35 @@ ve.dm.Document.isContentOffset = function( data, offset ) {
/** /**
* Checks if structure can be inserted at an offset in document data. * Checks if structure can be inserted at an offset in document data.
* *
* If the {unrestricted} param is true than only offsets where any kind of element can be inserted
* will return true. This can be used to detect the difference between a location that a paragraph
* can be inserted, such as between two tables but not direclty inside a table.
*
* This method assumes that any value that has a type property that's a string is an element object. * This method assumes that any value that has a type property that's a string is an element object.
* *
* @example Structural offsets: * @example Structural offsets (unrestricted = false):
* <heading> a </heading> <paragraph> b c <img> </img> </paragraph> * <heading> a </heading> <paragraph> b c <img> </img> </paragraph>
* ^ . . ^ . . . . . ^ * ^ . . ^ . . . . . ^
* *
* @example Structural offsets (unrestricted = true):
* <heading> a </heading> <paragraph> b c <img> </img> </paragraph>
* ^ . . ^ . . . . . ^
*
* @example Structural offsets (unrestricted = false):
* <list> <listItem> </listItem> <list>
* ^ ^ ^ ^ ^
*
* @example Content branch offsets (unrestricted = true):
* <list> <listItem> </listItem> <list>
* ^ . ^ . ^
*
* @static * @static
* @method * @method
* @param {Array} data Document data * @param {Array} data Document data
* @param {Integer} offset Document offset * @param {Integer} offset Document offset
* @returns {Boolean} Structure can be inserted at offset * @returns {Boolean} Structure can be inserted at offset
*/ */
ve.dm.Document.isStructuralOffset = function( data, offset ) { ve.dm.Document.isStructuralOffset = function( data, offset, unrestricted ) {
// Edges are always structural // Edges are always structural
if ( offset === 0 || offset === data.length ) { if ( offset === 0 || offset === data.length ) {
return true; return true;
@ -242,15 +258,31 @@ ve.dm.Document.isStructuralOffset = function( data, offset ) {
// Is a closing // Is a closing
left.type.charAt( 0 ) === '/' && left.type.charAt( 0 ) === '/' &&
// Is a branch // Is a branch
factory.canNodeHaveChildren( left.type.substr( 1 ) ) factory.canNodeHaveChildren( left.type.substr( 1 ) ) &&
(
// Only apply this rule in unrestricted mode
!unrestricted ||
// Right of an unrestricted branch
// <list><listItem><paragraph>a</paragraph>|</listItem></list>|
// Both are non-content branches that can have any kind of child
factory.getParentNodeTypes( left.type.substr( 1 ) ) === null
)
) || ) ||
// Left of branch // Left of a branch
// |<list>|<listItem>|<paragraph>a</paragraph></listItem></list> // |<list>|<listItem>|<paragraph>a</paragraph></listItem></list>
( (
// Is not a closing // Is not a closing
right.type.charAt( 0 ) !== '/' && right.type.charAt( 0 ) !== '/' &&
// Is a branch // Is a branch
factory.canNodeHaveChildren( right.type ) factory.canNodeHaveChildren( right.type ) &&
(
// Only apply this rule in unrestricted mode
!unrestricted ||
// Left of an unrestricted branch
// |<list><listItem>|<paragraph>a</paragraph></listItem></list>
// Both are non-content branches that can have any kind of child
factory.getParentNodeTypes( right.type ) === null
)
) || ) ||
// Inside empty non-content branch // Inside empty non-content branch
// <list>|</list> or <list><listItem>|</listItem></list> // <list>|</list> or <list><listItem>|</listItem></list>
@ -258,7 +290,13 @@ ve.dm.Document.isStructuralOffset = function( data, offset ) {
// Inside empty element // Inside empty element
'/' + left.type === right.type && '/' + left.type === right.type &&
// Both are non-content branches (right is the same type) // Both are non-content branches (right is the same type)
factory.canNodeHaveGrandchildren( left.type ) factory.canNodeHaveGrandchildren( left.type ) &&
(
// Only apply this rule in unrestricted mode
!unrestricted ||
// Both are non-content branches that can have any kind of child
factory.getChildNodeTypes( left.type ) === null
)
) )
) )
); );

View file

@ -554,14 +554,14 @@ test( 'isContentOffset', function() {
{ 'msg': 'between content branches', 'expected': false }, { 'msg': 'between content branches', 'expected': false },
{ 'msg': 'inside emtpy content branch', 'expected': true }, { 'msg': 'inside emtpy content branch', 'expected': true },
{ 'msg': 'between content branches', 'expected': false }, { 'msg': 'between content branches', 'expected': false },
{ 'msg': 'begining of content branch and left of inline leaf', 'expected': true }, { 'msg': 'begining of content branch, left of inline leaf', 'expected': true },
{ 'msg': 'inside content branch with only non-text inline leaf', 'expected': false }, { 'msg': 'inside content branch with non-text inline leaf', 'expected': false },
{ 'msg': 'end of content branch and right of block leaf', 'expected': true }, { 'msg': 'end of content branch, right of block leaf', 'expected': true },
{ 'msg': 'between content and non-content branches', 'expected': false }, { 'msg': 'between content, non-content branches', 'expected': false },
{ 'msg': 'between parent and child branches, descending', 'expected': false }, { 'msg': 'between parent, child branches, descending', 'expected': false },
{ 'msg': 'inside empty non-content branch', 'expected': false }, { 'msg': 'inside empty non-content branch', 'expected': false },
{ 'msg': 'between parent and child branches, ascending', 'expected': false }, { 'msg': 'between parent, child branches, ascending', 'expected': false },
{ 'msg': 'between non-content branch and block leaf', 'expected': false }, { 'msg': 'between non-content branch, block leaf', 'expected': false },
{ 'msg': 'inside block leaf', 'expected': false }, { 'msg': 'inside block leaf', 'expected': false },
{ 'msg': 'right of document', 'expected': false } { 'msg': 'right of document', 'expected': false }
]; ];
@ -594,30 +594,41 @@ test( 'isStructuralOffset', function() {
{ 'type': '/alienBlock' } { 'type': '/alienBlock' }
], ],
cases = [ cases = [
{ 'msg': 'left of document', 'expected': true }, { 'msg': 'left of document', 'expected': [true, true] },
{ 'msg': 'begining of content branch', 'expected': false }, { 'msg': 'begining of content branch', 'expected': [false, false] },
{ 'msg': 'left of non-text inline leaf', 'expected': false }, { 'msg': 'left of non-text inline leaf', 'expected': [false, false] },
{ 'msg': 'inside non-text inline leaf', 'expected': false }, { 'msg': 'inside non-text inline leaf', 'expected': [false, false] },
{ 'msg': 'right of non-text inline leaf', 'expected': false }, { 'msg': 'right of non-text inline leaf', 'expected': [false, false] },
{ 'msg': 'between characters', 'expected': false }, { 'msg': 'between characters', 'expected': [false, false] },
{ 'msg': 'end of content branch', 'expected': false }, { 'msg': 'end of content branch', 'expected': [false, false] },
{ 'msg': 'between content branches', 'expected': true }, { 'msg': 'between content branches', 'expected': [true, true] },
{ 'msg': 'inside emtpy content branch', 'expected': false }, { 'msg': 'inside emtpy content branch', 'expected': [false, false] },
{ 'msg': 'between content branches', 'expected': true }, { 'msg': 'between content branches', 'expected': [true, true] },
{ 'msg': 'begining of content branch and left of inline leaf', 'expected': false }, { 'msg': 'begining of content branch, left of inline leaf', 'expected': [false, false] },
{ 'msg': 'inside content branch with only non-text inline leaf', 'expected': false }, { 'msg': 'inside content branch with non-text inline leaf', 'expected': [false, false] },
{ 'msg': 'end of content branch and right of inline leaf', 'expected': false }, { 'msg': 'end of content branch, right of inline leaf', 'expected': [false, false] },
{ 'msg': 'between content and non-content branches', 'expected': true }, { 'msg': 'between content, non-content branches', 'expected': [true, true] },
{ 'msg': 'between parent and child branches, descending', 'expected': true }, { 'msg': 'between parent, child branches, descending', 'expected': [true, false] },
{ 'msg': 'inside empty non-content branch', 'expected': true }, { 'msg': 'inside empty non-content branch', 'expected': [true, true] },
{ 'msg': 'between parent and child branches, ascending', 'expected': true }, { 'msg': 'between parent, child branches, ascending', 'expected': [true, false] },
{ 'msg': 'between non-content branch and block leaf', 'expected': true }, { 'msg': 'between non-content branch, block leaf', 'expected': [true, true] },
{ 'msg': 'inside block leaf', 'expected': false }, { 'msg': 'inside block leaf', 'expected': [false, false] },
{ 'msg': 'right of document', 'expected': true } { 'msg': 'right of document', 'expected': [true, true] }
]; ];
expect( data.length + 1 ); expect( ( data.length + 1 ) * 2 );
for ( var i = 0; i < cases.length; i++ ) { for ( var i = 0; i < cases.length; i++ ) {
strictEqual( ve.dm.Document.isStructuralOffset( data, i ), cases[i].expected, cases[i].msg ); var left = data[i - 1] ? ( data[i - 1].type || data[i - 1][0] ) : '[start]',
right = data[i] ? ( data[i].type || data[i][0] ) : '[end]';
strictEqual(
ve.dm.Document.isStructuralOffset( data, i ),
cases[i].expected[0],
cases[i].msg + ' (' + left + '|' + right + ' @ ' + i + ')'
);
strictEqual(
ve.dm.Document.isStructuralOffset( data, i, true ),
cases[i].expected[1],
cases[i].msg + ', unrestricted (' + left + '|' + right + ' @ ' + i + ')'
);
} }
} ); } );
@ -654,14 +665,14 @@ test( 'isElementData', 1, function() {
{ 'msg': 'between content branches', 'expected': true }, { 'msg': 'between content branches', 'expected': true },
{ 'msg': 'inside emtpy content branch', 'expected': true }, { 'msg': 'inside emtpy content branch', 'expected': true },
{ 'msg': 'between content branches', 'expected': true }, { 'msg': 'between content branches', 'expected': true },
{ 'msg': 'begining of content branch and left of inline leaf', 'expected': true }, { 'msg': 'begining of content branch, left of inline leaf', 'expected': true },
{ 'msg': 'inside content branch with only non-text leaf', 'expected': true }, { 'msg': 'inside content branch with non-text leaf', 'expected': true },
{ 'msg': 'end of content branch and right of inline leaf', 'expected': true }, { 'msg': 'end of content branch, right of inline leaf', 'expected': true },
{ 'msg': 'between content and non-content branches', 'expected': true }, { 'msg': 'between content, non-content branches', 'expected': true },
{ 'msg': 'between parent and child branches, descending', 'expected': true }, { 'msg': 'between parent, child branches, descending', 'expected': true },
{ 'msg': 'inside empty non-content branch', 'expected': true }, { 'msg': 'inside empty non-content branch', 'expected': true },
{ 'msg': 'between parent and child branches, ascending', 'expected': true }, { 'msg': 'between parent, child branches, ascending', 'expected': true },
{ 'msg': 'between non-content branch and block leaf', 'expected': true }, { 'msg': 'between non-content branch, block leaf', 'expected': true },
{ 'msg': 'inside block leaf', 'expected': true }, { 'msg': 'inside block leaf', 'expected': true },
{ 'msg': 'right of document', 'expected': false } { 'msg': 'right of document', 'expected': false }
]; ];